PDF下載地址:
鏈接:https://pan.baidu.com/s/1l_FAbn_g04q-Z0lKSxx8tw
提取碼:8lbp
注:英文原文為《A Gentle Introduction to ROS》 【美】Jason M. O'Kane著
一、緒論

二、入門概述
1. 安裝:參考 http://wiki.ros.org/cn
(1)sudo rosdep init 初始化rosdep,rosdep用於檢查和安裝軟件包的依賴
(2)rosdep update 初始化rosdep,在根目錄下保存一些文件,文件夾名為.ros
(3)source /opt/ros/ros版本/setup.bash
- 設置ROS_PACKAGE_PATH等環境變量,ROS根據這些環境變量來定位文件 // 輸入 export | grep ROS 判斷是否配置完成
- setup.bash還定義了一些ros系統的bash函數,如roscd和rosls //這些函數定義在rosbash軟件包中
(3)測試ROS是否正確安裝
- roscore
- rosrun turtlesim turtlesim_node
- rosrun turtlesim turtle_teleop_key
注:三個終端分別執行指令
2. 常用術語和命令
(1)ROS功能包/軟件包:一組用於實現特定功能的相關文件的集合,包括可執行文件和其他支持文件 //任何能找到且包含package.xml文件的目錄
注:使用catkin編譯生成的可執行文件放在外部標准目錄devel中(源碼外編譯)
(2)節點node:ROS程序的運行實例 running instance
(3)rosrun命令:啟動ROS節點 rosrun 功能包名 可執行文件名 //節點名不一定與可執行文件名相同,可以使用 __name:=節點名 顯式設置節點名稱
- rosrun本質上是一個shell腳本,可以根據ROS的文件組織結構自動找到相應可執行文件 //可以直接輸入可執行文件的路徑啟動節點(等價的普通程序執行方式)
- 通過節點管理器master注冊成為ROS節點發生在程序的源碼內部,而不是通過rosrun命令
注:節點名稱必須唯一,不可多個節點擁有相同名稱
(4)消息傳遞機制:節點管理器負責確保發布節點和訂閱節點能找到對方,隨后消息從發布節點直接傳遞到訂閱節點,不經過節點管理器轉交
注:節點只管發,不管收,有助於減少節點之間的耦合度;話題可共享,話題和消息的通信機制是多對多的;服務是一對一的通信機制
(5)rosmsg show 消息類型名 //輸出消息類型的詳細信息
(6)rostopic pub 話題名 消息類型 消息內容 //命令行手動發布消息
注:消息內容可以按照rosmsg show命令的顯示結果進行輸入,也可以以YAML字典的形式輸入(顯式指明結構域中變量名和值的映射關系)
(7)每個消息類型均屬於一個特定的包,如turtlesim/Color屬於turtlesim包 //消息類型斜杠/前的是包名,斜杠/后是類型名
注:把包名包含在消息類型中的目的:
- 能避免命名沖突 // geometry_msgs/Pose 和 turtlesim/Pose
- 在用到其他包的消息類型時,更容易表明包之間的依賴關系
- 有助於理解消息類型的含義
三、編寫ROS程序
1. catkin編譯系統試圖一次性編譯同一個工作區中的所有功能包
2. 創建功能包:catkin_create_pkg 包名 //包名只允許使用小寫字母、數字和下划線,且首字符必須為字母
3. 清單文件package.xml中聲明的依賴庫並不會在編譯過程中進行檢查,但是會在將包發布給他人時,他人會因為沒有安裝依賴庫而編譯報錯
4. CMakeLists.txt中的${catkin_LIBRARIES}變量由find_package(catkin REQUIRED COMPONENTS ...)語句定義
5. source devel/setup.bash //專門為當前的工作區進行環境變量設置

6. 每個話題都有一個消息類型,而每個消息類型都有相應的C++頭文件 //包含消息類型頭文件 #include<包名/類型名.h> 如#include<geometry_msgs/Twist.h>
注:頭文件定義了一個與消息類型對應的C++類,且該類定義在以包名命名的域名空間中,如geometry_msgs::Twist類
7. ros::NodeHandle類維護一個引用計數,僅在第一個NodeHandle對象創建時才會在節點管理器注冊新的節點 //使用標准的roscpp接口,在單個程序中無法運行多個節點
8. 創建發布者ros::Publisher屬於耗時操作,應該每個話題創建一個發布者,在程序執行過程中一直使用相應發布者 //發布者創建不要放在循環內,避免為每條消息生成新的發布者
9. ros::ok()返回false條件:
- 使用rosnode kill命令終止節點
- 使用Ctrl+C發送終止信號 //需要采用rosnode cleanup從節點管理器的列表中刪除節點
- 調用ros::shutdown() //在代碼中顯式表明節點工作已完成
- 以相同節點名啟動新的節點 //新的節點將正常運行,而舊節點會被終止

10. 訂閱者回調函數:void function_name(const package_name::type_name &msg){ ... }
- 調用回調函數是ROS執行,返回值也交給ROS,用戶程序無法獲得返回值,因此設置為void
- 回調函數指針為 &function_name,如程序中的 &poseMessageReceived // 取地址符&是可選的,但建議使用,編譯器會根據函數名后面是否有括號,自動判斷是指針還是函數調用
11. 顯式調用 ros::spin()或ros::spinonce()讓ROS執行回調函數
四、日志消息 //ROS使用log4cxx庫實現日志功能
1. ROS日志系統的核心思想:使程序生成一些簡短的文本字符流,即日志消息
2. 嚴重級別(遞增):DEBUG、INFO、WARN、ERROR、FATAL

3. 產生日志消息的基本C++宏
- ROS_DEBUG_STREAM(message)
- ROS_INFO_STREAM(message)
- ROS_WARN_STREAM(message)
- ROS_ERROR_STREAM(message)
- ROS_FATAL_STREAM(message)
注:日志系統是面向行的,調用任意宏會生成完整的一行日志消息 //無需使用std::endl
4. 日志消息的輸出
(1)控制台
- DEBUG和INFO消息被送至標准輸出,WARN、ERROR和FATAL消息被送至標准錯誤
- 設置ROSCONSOLE_FORMAT環境變量調整日志消息打印到控制台的格式
- roslaunch工具默認不會將節點的標准輸出和標准錯誤導入至自己的輸入流,需顯式使用 output="screen"屬性或者--screen參數
(2)/rosout 話題:消息類型為rosgraph_msgs/Log
- 包含系統中所有節點的日志消息
- 查看消息內容:rostopic echo /rosout 或者 rqt_console

- rqt_console節點訂閱/rosout_agg話題 //后綴_agg表示消息是經過rosout節點聚合的結果
- /rosout話題發布的消息都通過rosout節點輸出到/rosout_agg話題
- rosout是唯一一個訂閱/rosout話題的節點,也是/rosout_agg話題唯一的發布者 //減少調試代價
注:rosout即表示話題,也表示節點
(3)日志文件:作為/rosout話題回調函數的一部分,由rosout節點生成
- 文件名類似於:~/.ros/log/run_id/rosout.log //純文本文件,用less、head、tail等命令查看
- 查看運行標識碼run_id //節點管理器開始運行時基於MAC地址和當前時間生成
- 查看roscore的輸出結果:setting /run_id to run_id
- 向節點管理器查詢:rosparam get /run_id //run_id存放在參數服務器中
檢查日志文件大小:rosclean check
刪除日志文件:rosclean purge
5. 設置日志級別
(1)通過命令行設置:rosservice call /node-name /set_logger_level ros.包名 level // level參數包括DEBUG、INFO、WARN、ERROR、FATAL
注:參數ros.包名指定期望配置的日志記錄器logger名稱
(2)通過圖形界面設置:rqt_logger_level
(3)通過C++代碼設置:調用ROS實現日志功能的log4cxx提供的接口 #include<log4cxx/logger.h>
五、計算圖源命名
1. 計算圖源graph resource:節點、話題、服務和參數的統稱,由短字符串表示的計算圖源名稱進行標識
2. 全局名稱:/turtle1/cmd_vel //優點:任何地方都可以使用;缺點:需要完整列出其所屬的命名空間
- 由前斜杠"/"作為首字符
- 由斜杠分開一系列命名空間,每個斜杠代表一級命名空間,如turtle1命名空間
- 命名空間用於將相關的計算圖源歸類在一起,ROS允許多層命名空間嵌套
- 最末為基本名稱base name,如cmd_vel
3. 相對名稱 //利用ROS提供的默認命名空間
(1)典型特征:缺少全局名稱帶有的前斜杠"/"
(2)解析相對名稱:將當前的默認命名空間名稱加在相對名稱之前,生成全局名稱,如 /turtle1 + cmd_vel => /turtle1/cmd_vel
(3)設置默認命名空間:單獨為每個節點設置 //不設置,則使用全局命名空間"/"作為默認命名空間
- 在啟動文件中使用命名空間ns屬性
- 利用命令行參數 __ns:=default-namespace
- 利用環境變量設置shell:export ROS_NAMESPACE=default-namespace //僅當未設置__ns參數時才有效
優點:避免在移植和整合來自不同來源的節點時發生名稱沖突
4. 私有名稱 //用於節點內部僅與本節點有關的資源,如參數
(1)典型特征:以一個波浪字符(~)開始
(2)與相對名稱一致,不能完全確定自身所在命名空間,需要利用ROS客戶端庫進行名稱解析
(3)不使用當前的默認命名空間,而采用節點名稱作為命名空間,如 /sim1/pubvel + ~max_vel => /sim1/pubvel/max_vel
(4)私有名稱可用於管理節點的服務,話題不能命名為私有名稱
注:私有名稱的關鍵字"private"僅表明不使用所在的命名空間,其他節點可以通過私有名稱解析后的全局名稱進行訪問 //僅在命名空間層面上有意義,注意與其他編程語言的不同
5. 匿名名稱:非用戶指定的無語義信息的名字
(1)一般用於為節點命名,使節點的命名保存唯一性
(2)調用ros::init方法時請求一個自動分配的唯一名稱
- ros::init(argc, argv, base_name, ros::init_options::AnonymousName);
注:ros::init使用處理器時間在節點的基本名稱后追加文本,保證名字的唯一性
六、啟動文件
1. 目的:利用啟動文件一次性配置和運行多個節點

2. 執行啟動文件:roslaunch 包名 啟動文件名 //如果沒有運行roscore,roslaunch會自動啟動roscore
(1)如果啟動文件不屬於任何功能包,則可以直接以啟動文件路徑啟動 roslaunch + 啟動文件路徑
(2)啟動文件內的節點幾乎是同一時刻啟動,無法確定啟動順序,因此節點之間應盡量保持獨立
(3)添加-v選項可以輸出詳細信息:roslaunch -v 包名 啟動文件名
(4)查找啟動文件時,roslaunch工具會同時搜索每個功能包目錄的子目錄
3. 啟動文件基本元素
(1)根元素:<launch> ... </launch>
(2)啟動節點:
<node
pkg=包名
type=可執行文件名
name=節點名
/>
- 標簽末尾的斜杠"/"不可少,另一種方式為 <node pkg="..." type="..." name="..."></node> //顯式給出結束標簽
- 必需屬性pkg,type,name(會覆蓋ros::init設置的名稱)
- 默認情況下,啟動文件啟動節點的標准輸出被重定向至日志文件~/.ros/log/run_id/node_name-number-stout.log中
- 在節點元素中配置屬性output="screen"可以在控制台終端輸出信息
- 使用--screen 命令行選項在控制台顯式所有節點輸出:roslaunch --screen 包名 啟動文件名
(3)請求復位:respawn="true" //當節點停止時,roslaunch會重新啟動該節點,可用於應對軟件崩潰、硬件故障等引起的節點中止
(4)必要節點:required="true" //當必要節點中止時,roslaunch會終止所有其他活躍節點,並退出;與respawn作用相互矛盾,不能同時配置
(5)roslaunch所有節點共享一個終端,針對依賴控制台輸入的節點來說,需要維護獨立的終端:使用啟動前綴屬性 launch-prefix="命令行前綴"
- roslaunch啟動節點時調用相應的命令行工具rosrun
- 啟動前綴相當於在rosrun前添加前綴,即示例中的launch-prefix="xterm -e" 等價於 xterm -e rosrun turtlesim turtle_teleop_key
3. 在命名空間內啟動節點 //通過配置節點元素的ns屬性,壓入命名空間


(1)盡管兩個節點相對名稱相同,但是由於命名空間不同,所以兩個節點相互獨立
(2)由於話題名稱定義時采用的也是相對名稱,所以話題也分別位於獨立的命名空間
4. 名稱重映射remapping names //從更精細的層面控制節點名稱的修改
(1)基於替換的思想:每個重映射包含一個原始名稱和一個新名稱,每當節點使用原始名稱時,ROS客戶端庫會將它自動替換為新名稱
(2)創建重映射
- 命令行啟動時,分別給出原始名稱和新名稱 原始名稱:=新名稱 //rosrun turtlesim turtlesim_node turtl1/pose:=tim
- 啟動文件中,使用重映射元素: <remap from="original-name" to "new-name"/> //若屬性作為launch元素的子元素出現在頂層,則應用到所有后續節點
注:重映射元素也可以作為節點的子元素:<node ...> <remap from="original-name" to "new-name"/> </node> 此時只應用於所在節點
注:
- ROS在應用任何重映射之前,所有的名稱需要先解析為全局名稱
- 應用示例:反向海龜 //將原始速度話題重映射為處理后的反向速度話題
5. 其他元素

(1)啟動文件的嵌套:include元素
- <include file="啟動文件完整路徑"/> //通常使用find命令搜索功能包位置,避免路徑輸入錯誤,如<include file="$(find package-name)/launch-file-name"/>
- 支持命名空間屬性,可以將包含內容壓入指定命名空間:<include file="..." ns="namespace"/>
(2)啟動參數argument:便於配置啟動文件,類似可執行程序中的局部變量(僅在聲明的當前啟動文件內有效,不可被包含的啟動文件繼承)
- 聲明參數:<arg name="arg-name"/> //聲明是可選的,有助於明確啟動文件的參數有哪些
- 參數賦值
- 命令行賦值 roslaunch 包名 啟動文件名 arg-name:=arg-value
- 啟動文件內聲明時賦值
-
- <arg name="arg-name" default="arg-value"/> //可被命令行參數覆蓋
- <arg name="arg-name" value="arg-value"/> //不可被命令行參數覆蓋
- 獲取參數值:$(arg arg-name)
- 向包含的次級啟動文件中發送參數值
- 將arg元素作為include元素的子元素 <include ...> <arg name="arg-name" value="arg-value"/> </include>
- 可使用<arg name="arg-name" value="$(arg arg-name)"/> 保證內外同名參數具有相同的參數值
注:在ROS中,parameter和argument是有區別的
- parameter是ROS系統運行過程中使用的數值,存儲在參數服務器parameter server中,可以被節點和用戶獲取(ros::param::get或rosparam)
- argument只在啟動文件內有意義,不能被節點直接獲取
(3)創建組group:大型啟動文件內管理節點的快捷方式
- 把若干節點放入同一命名空間中:<group ns="namespace"> ... </group>
- 可以有條件地啟動或禁用一個節點:<group if="0 or 1"> ... </group> //若屬性為1,則正常啟動,否則忽略組標簽內元素;若 if 改為 unless 則用法相反
注:僅有0和1為合法取值,且不能使用布爾運算;也可以不使用組元素,單獨為每個節點設置ns、if和unless
七、參數parameter //配置節點信息的集中式方法
1. 主要思想:使用集中的參數服務器維護一個變量集的值(字典),適用於不會隨時間頻繁變更的信息
2. 通過命令行獲取參數
(1)rosparam list //查看參數列表,輸出 /rosdistro 等全局計算圖源的名稱字符串
(2)rosparam get parameter_name //查詢參數,如 rosparam get /rosdistro 得到ROS版本
(3)rosparam get namespace //檢索給定命名空間每個參數的值,如 rosparam get / 查詢全局命名空間/
(4)rosparam set parameter_name parameter_value //設置參數,修改已有參數的值或創建新參數
(5)rosparam set namespace values //設置同一命名空間的多個參數,其中值values要以YAML字典的形式給出參數和值的對應關系
(6)創建和加載參數文件(YAML形式) //可用於場景復現
- rosparam dump filename namespace //存儲命名空間中的所有參數
- rosparam load filename namespace //讀取參數至參數服務器
注:命名空間參數可選,默認為全局命名空間/
注:
- 所有的參數都屬於參數服務器,而不是任何特定節點,即使節點終止時參數仍然存在
- 更新的參數值不會自動“推送”到節點,節點需要主動顯式地向參數服務器查詢參數值
3. 使用C++接口獲取參數 //參數名可以是全局的、相對的或者私有的
void ros::param::set(parameter_name, input_value);
bool ros::param::get(parameter_name, output_value); //讀取成功返回true,否則表明參數還未指定值
注:在命令行中實現私有參數賦值 rosrun agitr pubvel_with_max _max_vel:=1 //參數max_vel前面添加下划線_前綴 _param-name:=param-value
4. 在啟動文件中設置參數
(1)<param name="param-name" value="param-value"/> //設置參數,參數名是相對名稱
(2)<node ...> <param name="param-name" value="param-value"/> </node> //在節點元素內包含param元素,無論是否以~或/開頭,參數均為節點私有參數
(3)<rosparam command="load" file="path-to-param-file"/> //一次性從文件中加載多個參數,等價於rosparam load
注:通常采用 file="$(find package-name)/param-file" 指定文件路徑
八、服務service
1. 與話題消息的區別
- 服務調用是雙向的
- 服務調用是一對一通信
2. 執行過程:客戶端節點發送請求數據到服務器節點,並且等待回應;服務器節點收到請求后,執行相應活動,隨后發送響應數據給客戶端節點;
3. 與話題內容由消息數據類型確定類似,服務的內容也由服務數據類型決定
4. 從命令行查看和調用服務
(1)rosservice list //列出所有服務
(2)服務一般可以划分為兩類:
- 從特定節點獲取或向其傳遞消息 //通常采用節點名作為命名空間以防止命名沖突,如采用私用名稱的~get_loggers服務和~set_logger_level服務會被解析為/turtlesim/get_logger和/turtlesim/set_logger_level
- 不針對某些特定節點的服務 //例如/spawn服務用於生成一個新的仿真海龜,盡管由turtlesim節點提供,但是不屬於任何特定節點(只需要完成相應功能,而不關心是哪個節點在起作用)
5. rosnode info node-name //查看某個節點的服務類型
6. rosservice node service-name //查找提供服務的節點(反向查詢)
7. rosservice info service-name //確定服務的數據類型,通常也是由包名和類型名組成“包名/類型名”,如turtlesim/Spawn
8. rossrv show 服務數據類型名 //獲取服務數據類型的詳細信息(一系列數據域),注意rosservice和rossrv的區別
注:
- 短橫線---之前為請求的數據項,之后的字段為響應項
- 服務的請求和響應字段都可以為空,如turtlesim_node提供的/reset服務的數據類型為std_srvs/Empty,其請求和響應字段均為空
9. rosservice call service-name request-content //調用服務,提供請求域的所有值,如rosservice call /spawn 3 3 0 Mikey 在位置(3, 3)處創建一個名為"Mikey"的新海龜,朝向為0
注:
- 新海龜有自己的資源集,且位於新命名空間Mikey中 //避免命名沖突
- rosservice call的輸出為服務器節點的響應數據,如上例為 name: Mikey //調用成功或失敗
- rosservice call /clear 從參數服務器讀取參數值,刷新當前參數
10. 客戶端程序

(1)聲明請求和響應類型:#include<turtlesim/Spawn.h> //包含頭文件,定義turtlesim::Spawn類(服務數據類型)
(2)創建客戶端對象:ros::ServiceClient client = node_handle.serviceClient<service_type>(service_name) //服務名稱一般應當為相對名稱,如"spawn"
注:與話題發布者不同,無需緩沖隊列,會阻塞等待直至服務調用完成
(3)創建請求和響應對象 //上述頭文件中定義了請求和響應的類,通過包名和服務類型引用,如turtlesim::Spawn::
- package_name::service_type::Request //請求對象類,應做賦值操作
- package_name::service_type::Response //響應對象類,接收服務器響應消息,無需賦值
注:服務類型的頭文件中還定義了一個單獨類package_name::service_type,同時擁有Request和Response作為數據成員;可以定義單獨對象package_name::service_type srv,而不使用獨立的Request和Response對象
(4)調用服務:bool success = service_client.call(request, response)
- 完成定位服務器節點、傳輸請求數據、等待響應和存儲響應數據等一系列工作
- 調用方法返回布爾值來確認調用服務是否成功完成
注:
- 不要忘記對調用返回值進行判斷,可通過ROS_ERROR_STREAM輸出可能的錯誤信息
- 默認情況下,調用返回后,會關閉與服務器的連接;
- 可通過ros::ServiceClient client = node_handle.serviceClient<service_type>(service_name, true)建立持續連接的客戶端 //提高了節點耦合性,系統魯棒性變差,不建議
11. 服務器程序

(1)編寫回調函數 bool function_name(package_name::service_type::Request &req, package_name::service_type::Response &resp)
注:本例中請求和響應的數據類型均為std_srvs/Empty(空字符串),無需進行數據處理
(2)與話題訂閱者類似,需要創建服務器對象ros::ServiceServer server=node_handle.advertiseService(service_name, pointer_to_callback_function);
- service_name 建議使用局部名稱,但不嚴格限制使用全局名稱
- ros::NodeHandle::advertiseService拒絕接受私有名稱,即以波浪號~開頭的名稱
- 可以利用節點自身的缺省命名空間創建私有名稱服務
- ros::NodeHandle nhPrivate("~"); //發送給此節點句柄的任意局部名稱的缺省命名空間與節點名稱一致 (此句柄+相對名稱 效果等價於 私有名稱)
- ros::ServiceServer server=nhPrivate.advertiseService("baz", Callback); //與廣播名為~baz的服務效果相同
注:本例使用ros::spinOnce()而非ros::spin(),原因在於沒有服務調用時還需要執行其他工作,即發布速度指令 //與第三章 表3.5 進行對比
(3)解決ros::spinOnce() + sleep()引起的延遲問題
- 采用雙線程:一個負責發布消息,一個負責處理服務器回調
- 采用ros::spin(),並利用計數器回調函數(timer callback)發布消息 // http://wiki.ros.org/roscpp/Overview/Timers
九、消息錄制與回放
1. rosbag:將發布在一個或多個話題上的消息錄制到一個包文件中,可用於事后回放
注:術語包文件bag files指用於存儲帶時間戳的ROS消息的特殊文件格式
2. 命令行工具
(1)錄制包文件:rosbag record -O filename.bag topic-names //不指定文件名時,rosbag基於當前的日期和時間自動生成文件名;會創建新節點 /record_...
- rosbag record -a 記錄當前發布的所有話題消息
- rosbag record -j 啟動包文件的壓縮
(2)回放包文件:rosbag play filename.bag //保持與原始發布相同的順序和時間間隔
注:盡量避免系統中rosbag和“真實”節點向同一個話題發布消息
(3)檢查文件包:rosbag info filename.bag //提供包文件的豐富信息
3. rosbag功能包
(1)rosrun rosbag record -O filename.bag topic-names;rosrun rosbag play filename.bag //利用record和play可執行文件
(2)在啟動文件中使用包文件
- 錄制節點:<node pkg="rosbag" name="record" type="record" args='-O filename.bag topic-names"/>
- 回放節點:<node pkg="rosbag" name="play" type="play" args="filename.bag"/>
注:需要提供參數
第十章 總結
1. 在網絡環境中運行ROS:分布式機器人控制模式
(1)網絡層配置:確保計算機之間能夠互相通信
(2)ROS層配置:確保所有節點都能與節點管理器通信
2. 編寫更規范的程序,如使用ros::Timer的回調函數替代ros::Rate對象
3. 使用rviz是數據可視化:訂閱用戶話題以顯示消息
4. 創建消息和服務類型
5. 使用tf工具來管理多個坐標系:幫助節點完成坐標轉換
6. 使用Gazebo仿真:高保真的機器人仿真器
