聲明:本篇文章是學習wiki ROS上的相應教程。
第一步:創建一個catkin程序包(如過已經配置好catkin那就可跳過這一步)
本部分教程將演示如何使用catkin_create_pkg命令來創建一個新的catkin程序包以及創建之后都能做些什么。
首先切換到之前通過創建catkin工作空間教程創建的catkin工作空間中的src目錄下:
# You should have created this in the Creating a Workspace Tutorial
$ cd ~/catkin_ws/src
現在使用catkin_create_pkg命令來創建一個名為'beginner_tutorials'的新程序包,這個程序包依賴於std_msgs、roscpp和rospy:
$ catkin_create_pkg beginner_tutorials std_msgs rospy roscpp
這將會創建一個名為beginner_tutorials的文件夾,這個文件夾里面包含一個package.xml文件和一個CMakeLists.txt文件,這兩個文件都已經自動包含了部分你在執行catkin_create_pkg命令時提供的信息。
catkin_create_pkg命令會要求你輸入package_name,如果有需要你還可以在后面添加一些需要依賴的其它程序包:
# This is an example, do not try to run this
# catkin_create_pkg <package_name> [depend1] [depend2] [depend3]
catkin_create_pkg命令也有更多的高級功能,這些功能在catkin/commands/catkin_create_pkg中有描述。
第二步:編寫簡單的消息發布器和訂閱器 (C++)
http://wiki.ros.org/cn/ROS/Tutorials/WritingPublisherSubscriber%28c%2B%2B%29
1.編寫發布器節點
"節點(Node)" 是ROS中指代連接到ROS網絡的可執行文件的術語。接下來,我們將會創建一個發布器節點("talker"),它將不斷的在ROS網絡中廣播消息。
轉移到之前教程在catkin工作空間所創建的beginner_tutorials package路徑下:
cd ~/catkin_ws/src/beginner_tutorials
1.1源代碼
在beginner_tutorials package路徑下創建src目錄:
mkdir -p ~/catkin_ws/src/beginner_tutorials/src
這個目錄將會存儲beginner_tutorials package的所有源代碼.
在beginner_tutorials package里創建src/talker.cpp文件,並粘貼如下代碼:
https://raw.github.com/ros/ros_tutorials/groovy-devel/roscpp_tutorials/talker/talker.cpp
27 #include "ros/ros.h" 28 #include "std_msgs/String.h" 29 30 #include <sstream> 31 32 /** 33 * This tutorial demonstrates simple sending of messages over the ROS system. 34 */ 35 int main(int argc, char **argv) 36 { 37 /** 38 * The ros::init() function needs to see argc and argv so that it can perform 39 * any ROS arguments and name remapping that were provided at the command line. For programmatic 40 * remappings you can use a different version of init() which takes remappings 41 * directly, but for most command-line programs, passing argc and argv is the easiest 42 * way to do it. The third argument to init() is the name of the node. 43 * 44 * You must call one of the versions of ros::init() before using any other 45 * part of the ROS system. 46 */ 47 ros::init(argc, argv, "talker"); 48 49 /** 50 * NodeHandle is the main access point to communications with the ROS system. 51 * The first NodeHandle constructed will fully initialize this node, and the last 52 * NodeHandle destructed will close down the node. 53 */ 54 ros::NodeHandle n; 55 56 /** 57 * The advertise() function is how you tell ROS that you want to 58 * publish on a given topic name. This invokes a call to the ROS 59 * master node, which keeps a registry of who is publishing and who 60 * is subscribing. After this advertise() call is made, the master 61 * node will notify anyone who is trying to subscribe to this topic name, 62 * and they will in turn negotiate a peer-to-peer connection with this 63 * node. advertise() returns a Publisher object which allows you to 64 * publish messages on that topic through a call to publish(). Once 65 * all copies of the returned Publisher object are destroyed, the topic 66 * will be automatically unadvertised. 67 * 68 * The second parameter to advertise() is the size of the message queue 69 * used for publishing messages. If messages are published more quickly 70 * than we can send them, the number here specifies how many messages to 71 * buffer up before throwing some away. 72 */ 73 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000); 74 75 ros::Rate loop_rate(10); 76 77 /** 78 * A count of how many messages we have sent. This is used to create 79 * a unique string for each message. 80 */ 81 int count = 0; 82 while (ros::ok()) 83 { 84 /** 85 * This is a message object. You stuff it with data, and then publish it. 86 */ 87 std_msgs::String msg; 88 89 std::stringstream ss; 90 ss << "hello world " << count; 91 msg.data = ss.str(); 92 93 ROS_INFO("%s", msg.data.c_str()); 94 95 /** 96 * The publish() function is how you send messages. The parameter 97 * is the message object. The type of this object must agree with the type 98 * given as a template parameter to the advertise<>() call, as was done 99 * in the constructor above. 100 */ 101 chatter_pub.publish(msg); 102 103 ros::spinOnce(); 104 105 loop_rate.sleep(); 106 ++count; 107 } 108 109 110 return 0; 111 }
1.2代碼解釋
現在,我們來分段解釋代碼.
27 #include "ros/ros.h" 28
ros/ros.h是一個實用的頭文件,它引用了ROS系統中大部分常用的頭文件,使用它會使得編程很簡便。
28 #include "std_msgs/String.h" 29
這引用了std_msgs/String 消息, 它存放在std_msgs package里,是由String.msg文件自動生成的頭文件。需要更詳細的消息定義,參考msg頁面.
47 ros::init(argc, argv, "talker");
初始化ROS。它允許ROS通過命令行進行名稱重映射——目前,這不是重點。同樣,我們也在這里指定我們節點的名稱——必須唯一。
這里的名稱必須是一個base name,不能包含/。
54 ros::NodeHandle n;
為這個進程的節點創建一個句柄。第一個創建的NodeHandle會為節點進行初始化,最后一個銷毀的會清理節點使用的所有資源。
73 ros::Publisher chatter_pub = n.advertise<std_msgs::String>("chatter", 1000);
告訴master我們將要在chatter topic上發布一個std_msgs/String的消息。這樣master就會告訴所有訂閱了chatter topic的節點,將要有數據發布。第二個參數是發布序列的大小。在這樣的情況下,如果我們發布的消息太快,緩沖區中的消息在大於1000個的時候就會開始丟棄先前發布的消息。
NodeHandle::advertise() 返回一個 ros::Publisher對象,它有兩個作用: 1) 它有一個publish()成員函數可以讓你在topic上發布消息; 2) 如果消息類型不對,它會拒絕發布。
75 ros::Rate loop_rate(10);
ros::Rate對象可以允許你指定自循環的頻率。它會追蹤記錄自上一次調用Rate::sleep()后時間的流逝,並休眠直到一個頻率周期的時間。
在這個例子中,我們讓它以10hz的頻率運行。
81 int count = 0; 82 while (ros::ok()) 83 {
roscpp會默認安裝一個SIGINT句柄,它負責處理Ctrl-C鍵盤操作——使得ros::ok()返回FALSE。
ros::ok()返回false,如果下列條件之一發生:
- SIGINT接收到(Ctrl-C)
- 被另一同名節點踢出ROS網絡
-
ros::shutdown()被程序的另一部分調用
-
所有的ros::NodeHandles都已經被銷毀
一旦ros::ok()返回false, 所有的ROS調用都會失效。
我們使用一個由msg file文件產生的‘消息自適應’類在ROS網絡中廣播消息。現在我們使用標准的String消息,它只有一個數據成員"data"。當然你也可以發布更復雜的消息類型。
101 chatter_pub.publish(msg);
現在我們已經向所有連接到chatter topic的節點發送了消息。
93 ROS_INFO("%s", msg.data.c_str());
ROS_INFO和類似的函數用來替代printf/cout. 參考rosconsole documentation以獲得更詳細的信息。
103 ros::spinOnce();
在這個例子中並不是一定要調用ros::spinOnce(),因為我們不接受回調。然而,如果你想拓展這個程序,卻又沒有在這調用ros::spinOnce(),你的回調函數就永遠也不會被調用。所以,在這里最好還是加上這一語句。
105 loop_rate.sleep();
這條語句是調用ros::Rate對象來休眠一段時間以使得發布頻率為10hz。
對上邊的內容進行一下總結:
- 初始化ROS系統
-
在ROS網絡內廣播我們將要在chatter topic上發布std_msgs/String消息
- 以每秒10次的頻率在chatter上發布消息
接下來我們要編寫一個節點來接收消息。
2.編寫訂閱器節點
2.1源代碼
在beginner_tutorials package目錄下創建src/listener.cpp文件,並粘貼如下代碼:
https://raw.github.com/ros/ros_tutorials/groovy-devel/roscpp_tutorials/listener/listener.cpp
28 #include "ros/ros.h" 29 #include "std_msgs/String.h" 30 31 /** 32 * This tutorial demonstrates simple receipt of messages over the ROS system. 33 */ 34 void chatterCallback(const std_msgs::String::ConstPtr& msg) 35 { 36 ROS_INFO("I heard: [%s]", msg->data.c_str()); 37 } 38 39 int main(int argc, char **argv) 40 { 41 /** 42 * The ros::init() function needs to see argc and argv so that it can perform 43 * any ROS arguments and name remapping that were provided at the command line. For programmatic 44 * remappings you can use a different version of init() which takes remappings 45 * directly, but for most command-line programs, passing argc and argv is the easiest 46 * way to do it. The third argument to init() is the name of the node. 47 * 48 * You must call one of the versions of ros::init() before using any other 49 * part of the ROS system. 50 */ 51 ros::init(argc, argv, "listener"); 52 53 /** 54 * NodeHandle is the main access point to communications with the ROS system. 55 * The first NodeHandle constructed will fully initialize this node, and the last 56 * NodeHandle destructed will close down the node. 57 */ 58 ros::NodeHandle n; 59 60 /** 61 * The subscribe() call is how you tell ROS that you want to receive messages 62 * on a given topic. This invokes a call to the ROS 63 * master node, which keeps a registry of who is publishing and who 64 * is subscribing. Messages are passed to a callback function, here 65 * called chatterCallback. subscribe() returns a Subscriber object that you 66 * must hold on to until you want to unsubscribe. When all copies of the Subscriber 67 * object go out of scope, this callback will automatically be unsubscribed from 68 * this topic. 69 * 70 * The second parameter to the subscribe() function is the size of the message 71 * queue. If messages are arriving faster than they are being processed, this 72 * is the number of messages that will be buffered up before beginning to throw 73 * away the oldest ones. 74 */ 75 ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback); 76 77 /** 78 * ros::spin() will enter a loop, pumping callbacks. With this version, all 79 * callbacks will be called from within this thread (the main one). ros::spin() 80 * will exit when Ctrl-C is pressed, or the node is shutdown by the master. 81 */ 82 ros::spin(); 83 84 return 0; 85 }
2.2代碼解釋
下面我們將逐條解釋代碼,當然,之前解釋過的代碼就不再贅述了。
34 void chatterCallback(const std_msgs::String::ConstPtr& msg) 35 { 36 ROS_INFO("I heard: [%s]", msg->data.c_str()); 37 }
這是一個回調函數,當消息到達chatter topic的時候就會被調用。消息是以 boost shared_ptr指針的形式傳輸,這就意味着你可以存儲它而又不需要復制數據
75 ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);
告訴master我們要訂閱chatter topic上的消息。當有消息到達topic時,ROS就會調用chatterCallback()函數。第二個參數是隊列大小,以防我們處理消息的速度不夠快,在緩存了1000個消息后,再有新的消息到來就將開始丟棄先前接收的消息。
NodeHandle::subscribe()返回ros::Subscriber對象,你必須讓它處於活動狀態直到你不再想訂閱該消息。當這個對象銷毀時,它將自動退訂 上的消息。
有各種不同的NodeHandle::subscribe()函數,允許你指定類的成員函數,甚至是Boost.Function對象可以調用的任何數據類型。roscpp overview 提供了更為詳盡的信息。
82 ros::spin();
ros::spin()進入自循環,可以盡可能快的調用消息回調函數。如果沒有消息到達,它不會占用很多CPU,所以不用擔心。一旦ros::ok()返回FALSE,ros::spin()就會立刻跳出自循環。這有可能是ros::shutdown()被調用,或者是用戶按下了Ctrl-C,使得master告訴節點要shutdown。也有可能是節點被人為的關閉。
還有其他的方法進行回調,但在這里我們不涉及。想要了解,可以參考roscpp_tutorials package里的一些demo應用。需要更為詳盡的信息,參考roscpp overview。
下邊,我們來總結一下:
- 初始化ROS系統
-
訂閱chatter topic
- 進入自循環,等待消息的到達
-
當消息到達,調用chatterCallback()函數
3.編譯節點
之前教程中使用catkin_create_pkg創建了package.xml 和 CMakeLists.txt 文件。
生成的CMakeLists.txt看起來應該是這樣(在Creating Msgs and Srvs教程中的修改和未被使用的注釋和例子都被移除了):
1 cmake_minimum_required(VERSION 2.8.3) 2 project(beginner_tutorials) 3 4 ## Find catkin and any catkin packages 5 find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg) 6 7 ## Declare ROS messages and services 8 add_message_files(DIRECTORY msg FILES Num.msg) 9 add_service_files(DIRECTORY srv FILES AddTwoInts.srv) 10 11 ## Generate added messages and services 12 generate_messages(DEPENDENCIES std_msgs) 13 14 ## Declare a catkin package 15 catkin_package()
在CMakeLists.txt文件末尾加入幾條語句:
include_directories(include ${catkin_INCLUDE_DIRS})
add_executable(talker src/talker.cpp) target_link_libraries(talker ${catkin_LIBRARIES}) add_executable(listener src/listener.cpp) target_link_libraries(listener ${catkin_LIBRARIES})
結果,CMakeLists.txt文件看起來像這樣:
1 cmake_minimum_required(VERSION 2.8.3) 2 project(beginner_tutorials) 3 4 ## Find catkin and any catkin packages 5 find_package(catkin REQUIRED COMPONENTS roscpp rospy std_msgs genmsg) 6 7 ## Declare ROS messages and services 8 add_message_files(FILES Num.msg) 9 add_service_files(FILES AddTwoInts.srv) 10 11 ## Generate added messages and services 12 generate_messages(DEPENDENCIES std_msgs) 13 14 ## Declare a catkin package 15 catkin_package() 16 17 ## Build talker and listener 18 include_directories(include ${catkin_INCLUDE_DIRS}) 19 20 add_executable(talker src/talker.cpp) 21 target_link_libraries(talker ${catkin_LIBRARIES}) 22 add_dependencies(talker beginner_tutorials_generate_messages_cpp) 23 24 add_executable(listener src/listener.cpp) 25 target_link_libraries(listener ${catkin_LIBRARIES}) 26 add_dependencies(listener beginner_tutorials_generate_messages_cpp)
這會生成兩個可執行文件, talker 和 listener, 默認存儲到devel space目錄,具體是在~/catkin_ws/devel/lib/<package name>中.
現在要為可執行文件添加對生成的消息文件的依賴:
add_dependencies(talker beginner_tutorials_generate_messages_cpp)
這樣就可以確保自定義消息的頭文件在被使用之前已經被生成。因為catkin把所有的package並行的編譯,所以如果你要使用其他catkin工作空間中其他package的消息,你同樣也需要添加對他們各自生成的消息文件的依賴。當然,如果在*Groovy*版本下,你可以使用下邊的這個變量來添加對所有必須的文件依賴:
add_dependencies(talker ${catkin_EXPORTED_TARGETS})
你可以直接調用可執行文件,也可以使用rosrun來調用他們。他們不會被安裝到'<prefix>/bin'路徑下,因為那樣會改變系統的PATH環境變量。如果你確定要將可執行文件安裝到該路徑下,你需要設置安裝目標,請參考catkin/CMakeLists.txt
需要關於CMakeLists.txt更詳細的信息,請參考catkin/CMakeLists.txt
現在運行 catkin_make:
# In your catkin workspace
$ catkin_make
注意:如果你是添加了新的package,你需要通過--force-cmake選項告訴catkin進行強制編譯。參考catkin/Tutorials/using_a_workspace#With_catkin_make.
既然已經編寫好了發布器和訂閱器,下面讓我們來測試消息發布器和訂閱器.
第三步:編譯ROS程序包
1.編譯程序包
一旦安裝了所需的系統依賴項,我們就可以開始編譯剛才創建的程序包了。
注意: 如果你是通過apt或者其它軟件包管理工具來安裝ROS的,那么系統已經默認安裝好所有依賴項。
記得事先source你的環境配置(setup)文件,在Ubuntu中的操作指令如下:
$ source /opt/ros/groovy/setup.bash
使用 catkin_make
catkin_make 是一個命令行工具,它簡化了catkin的標准工作流程。你可以認為catkin_make是在CMake標准工作流程中依次調用了cmake 和 make。
使用方法:
# 在catkin工作空間下
$ catkin_make [make_targets] [-DCMAKE_VARIABLES=...]
CMake標准工作流程主要可以分為以下幾個步驟:
注意: 如果你運行以下命令是無效的,因為它只是一個演示CMake工作流程的例子。
# 在一個CMake項目里
$ mkdir build $ cd build $ cmake .. $ make $ make install # (可選)
每個CMake工程在編譯時都會執行這個操作過程。相反,多個catkin項目可以放在工作空間中一起編譯,工作流程如下:
# In a catkin workspace
$ catkin_make $ catkin_make install # (可選)
上述命令會編譯src文件夾下的所有catkin工程。想更深入了解請參考REP128。 如果你的源代碼不在默認工作空間中(~/catkin_ws/src),比如說存放在了my_src中,那么你可以這樣來使用catkin_make:
注意: 運行以下命令時無效的,因為my_src不存在。
# In a catkin workspace
$ catkin_make --source my_src $ catkin_make install --source my_src # (optionally)
對於catkin_make更高級的使用方法,請參考catkin/commands/catkin_make
1.1開始編譯你的程序包
按照之前的創建一個ROS程序包教程,你應該已經創建好了一個catkin 工作空間 和一個名為beginner_tutorials的catkin 程序包。現在切換到catkin workspace 並查看src文件夾:
$ cd ~/catkin_ws/
$ ls src
-
beginner_tutorials/ CMakeLists.txt@
你可以看到一個名為beginner_tutorials的文件夾,這就是你在之前的 catkin_create_pkg教程里創建的。現在我們可以使用catkin_make來編譯它了:
$ catkin_make
你可以看到很多cmake 和 make 輸出的信息:
-
Base path: /home/user/catkin_ws Source space: /home/user/catkin_ws/src Build space: /home/user/catkin_ws/build Devel space: /home/user/catkin_ws/devel Install space: /home/user/catkin_ws/install #### #### Running command: "cmake /home/user/catkin_ws/src -DCATKIN_DEVEL_PREFIX=/home/user/catkin_ws/devel -DCMAKE_INSTALL_PREFIX=/home/user/catkin_ws/install" in "/home/user/catkin_ws/build" #### -- The C compiler identification is GNU 4.2.1 -- The CXX compiler identification is Clang 4.0.0 -- Checking whether C compiler has -isysroot -- Checking whether C compiler has -isysroot - yes -- Checking whether C compiler supports OSX deployment target flag -- Checking whether C compiler supports OSX deployment target flag - yes -- Check for working C compiler: /usr/bin/gcc -- Check for working C compiler: /usr/bin/gcc -- works -- Detecting C compiler ABI info -- Detecting C compiler ABI info - done -- Check for working CXX compiler: /usr/bin/c++ -- Check for working CXX compiler: /usr/bin/c++ -- works -- Detecting CXX compiler ABI info -- Detecting CXX compiler ABI info - done -- Using CATKIN_DEVEL_PREFIX: /tmp/catkin_ws/devel -- Using CMAKE_PREFIX_PATH: /opt/ros/groovy -- This workspace overlays: /opt/ros/groovy -- Found PythonInterp: /usr/bin/python (found version "2.7.1") -- Found PY_em: /usr/lib/python2.7/dist-packages/em.pyc -- Found gtest: gtests will be built -- catkin 0.5.51 -- BUILD_SHARED_LIBS is on -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- ~~ traversing packages in topological order: -- ~~ - beginner_tutorials -- ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ -- +++ add_subdirectory(beginner_tutorials) -- Configuring done -- Generating done -- Build files have been written to: /home/user/catkin_ws/build #### #### Running command: "make -j4" in "/home/user/catkin_ws/build" ####
catkin_make首先輸出它所使用到的每個空間所在的路徑。更多關於空間的信息,請參考REP128和catkin/workspaces。需要注意的是由於這些空間存在默認配置的原因,有幾個文件夾已經在catkin工作空間自動生成了,使用ls查看:
$ ls
-
build devel src
build 目錄是build space的默認所在位置,同時cmake 和 make也是在這里被調用來配置並編譯你的程序包。devel 目錄是devel space的默認所在位置, 同時也是在你安裝程序包之前存放可執行文件和庫文件的地方。
第四步:測試消息發布器和訂閱器(http://wiki.ros.org/cn/ROS/Tutorials/ExaminingPublisherSubscriber)
Description: 本教程將測試上一教程所寫的消息發布器和訂閱器。
Tutorial Level: BEGINNER
Next Tutorial: 寫一個簡單的服務端和客戶端 (python) (c++)
1.啟動發布器
確保roscore可用,並運行:
$ roscore
catkin specific 如果使用catkin,確保你在調用catkin_make后,在運行你自己的程序前,已經source了catkin工作空間下的setup.sh文件:
# In your catkin workspace
$ cd ~/catkin_ws $ source ./devel/setup.bash
In the last tutorial we made a publisher called "talker". Let's run it:
$ rosrun beginner_tutorials talker (C++)
$ rosrun beginner_tutorials talker.py (Python)
你將看到如下的輸出信息:
-
[INFO] [WallTime: 1314931831.774057] hello world 1314931831.77 [INFO] [WallTime: 1314931832.775497] hello world 1314931832.77 [INFO] [WallTime: 1314931833.778937] hello world 1314931833.78 [INFO] [WallTime: 1314931834.782059] hello world 1314931834.78 [INFO] [WallTime: 1314931835.784853] hello world 1314931835.78 [INFO] [WallTime: 1314931836.788106] hello world 1314931836.79
發布器節點已經啟動運行。現在需要一個訂閱器節點來接受發布的消息。
2.啟動訂閱器
上一教程,我們編寫了一個名為"listener"的訂閱器節點。現在運行它:
# In your catkin workspace
$ cd ~/catkin_ws
$ source ./devel/setup.bash #如果有打開新的終端來運行就要從新在啟動。
$ rosrun beginner_tutorials listener (C++)
$ rosrun beginner_tutorials listener.py (Python)
你將會看到如下的輸出信息:
-
[INFO] [WallTime: 1314931969.258941] /listener_17657_1314931968795I heard hello world 1314931969.26 [INFO] [WallTime: 1314931970.262246] /listener_17657_1314931968795I heard hello world 1314931970.26 [INFO] [WallTime: 1314931971.266348] /listener_17657_1314931968795I heard hello world 1314931971.26 [INFO] [WallTime: 1314931972.270429] /listener_17657_1314931968795I heard hello world 1314931972.27 [INFO] [WallTime: 1314931973.274382] /listener_17657_1314931968795I heard hello world 1314931973.27 [INFO] [WallTime: 1314931974.277694] /listener_17657_1314931968795I heard hello world 1314931974.28 [INFO] [WallTime: 1314931975.283708] /listener_17657_1314931968795I heard hello world 1314931975.28
你已經測試完了發布器和訂閱器,下面我們來編寫一個服務和客戶端(python) (c++).
