ROS包通過catkin_create_pkg建立,是實作系統的基本單元。根據對資訊的傳遞和處理方式,ROS包可以分為消息模式和服務模式;根據編譯後的調用方法,又可分為可執行包和插件包。ROS包的實作大多在上述的分類範圍内。
1、ROS包消息/服務模式與要點
從功能上看,ROS包是資訊互動和處理的基本單元。根據資訊的互動和處理方式,ROS包有以下兩大類:
- 消息釋出者與訂閱者
- 伺服器與用戶端
對于消息模式的包,資訊的提供者主動,資訊的使用者被動:資訊使用者接收到消息後執行回調函數處理資訊,即處理資訊的回調函數由資訊釋出者觸發;
對于服務模式的包,資訊的提供者被動,資訊的使用者主動:資訊使用者需要時向資訊提供者查詢,提供者收到查詢參數後,調用回調函數計算結果然後給予應答,即處理資訊的回調函數由服務的查詢者(資訊使用者)觸發;
要點如下:
1.1 消息釋出與訂閱
消息的定義方法:
- 消息定義于包目錄的msg/xxx.msg中
- 定義消息的包可以與釋出者/訂閱者的包不一樣
- 在消息定義包的package.xml中加上build_depend和run_depend限制項
- 在消息定義包的CMakeLists.txt中設定好find_package(), catkin_package(), add_message_files(), generate_messages()
消息的釋出
- 完成節點初始化 ros::init()
- 建立節點句柄 ros::NodeHandle
- 調用NodeHandle的advertise方法建立消息釋出者
- 調用消息釋出者的publish方法釋出消息
消息的訂閱
- 建立訂閱到消息後需要執行的回調函數
- 完成節點初始化 ros::init()
- 建立節點句柄 ros::NodeHandle
- 調用NodeHandle的subscribe方法建立消息訂閱者并綁定回調函數
- ros::spin()
消息釋出和訂閱的指令行工具
通過指令行釋出和訂閱消息,不用通過ROS節點,測試的時候非常友善:
1.
rosmsg show [message type]
顯示消息的資料格式
2.
rostopic echo /topic_name
顯示特定topic接收到的消息
3.
rostopic pub <topic-name> <topic-type> [data...]
向特定的topic發送消息
1.2 伺服器與用戶端
服務的定義方法:
- 服務定義于包目錄的srv/xxx.srv中
- 定義服務的包可以與伺服器/用戶端的包不一樣
- 在消息定義包的CMakeLists.txt中設定好find_package(), add_service_files(), generate_messages()
伺服器
- 建立處理服務資料的回調函數
- 完成節點初始化 ros::init()
- 建立節點句柄 ros::NodeHandle
- 調用節點句柄的advertiseService函數啟動服務
- ros::spin()
用戶端
- 完成節點初始化 ros::init()
- 建立節點句柄 ros::NodeHandle
- 調用節點句柄的serviceClient函數建立用戶端對象client,通過服務名稱綁定服務
- 申明服務類型的對象(srv),并将srv.request部分賦初值
- 調用client的call(srv)函數調用服務
- 服務執行成功後,通過srv.response取得伺服器的應答結果
指令行工具
-
調用特定服務rosservice call <service-name> [service-args]
1.3 需要注意的細節
- 消息/服務模式的節點實作時要include消息/服務對應的頭檔案
- 上述頭檔案需要先編譯消息定義包來生成,是以需要在消息/服務使用者對應的CMakeLists.txt中加上add_dependencies()限制,否則會出現找不到頭檔案的現象
- 資訊互動中的被動方,即消息模式的消息訂閱者或服務模式的伺服器在都要調用ros::spin(),該函數確定回調能夠發生。
2、可執行包/插件包及要點
剛剛從資訊互動和處理角度介紹了ROS包的兩種模式,不管是哪種模式的ROS包,根據編譯後調用的方式,都可以分為以下兩種情況:
- 可執行包
- 插件包
可執行包可以通過rosrun直接調用;
插件包類似于動态連結庫,提供特定的功能。配合軟體總線的方式動态加載與解除安裝,可以實作功能的可重構;
這兩種包在實作上的差別主要展現在編譯指導語句方面。
2.1 可執行包
在對應包的CMakeLists.txt中添加
add_executable(<exec_name> src/xxx.cpp)
target_link_libraries(<exec_name> ${catkin_LIBRARIES})
add_dependencies(<exec_name> <msg/srv_pkg_name>_gencpp)
其中,
<exec_name>
為可執行檔案的名字,xxx.cpp是源代碼名字,msg/srv_pkg_name為所用到的消息/服務的對應包的名字。
編譯後,可執行包可以通過rosrun直接執行。
2.2 插件包
插件的定義方法
- 定義一個基類,所有的插件将繼承這一基類。在該基類中以公有成員函數(虛函數)的形式定義插件所具備的共同的接口。基類放到一個.h檔案中,該檔案可以與具體插件的實作不在一個ROS包中
- 定義具體的插件類,繼承上述基類,放到.h檔案中
- 在.cpp檔案中實作插件類的具體功能,并在該.cpp檔案中額外加上兩行:
#include <pluginlib/class_list_macros.h> PLUGINLIB_EXPORT_CLASS(<plugin_namespace::plugin_class>, <baseClass_namespace::base_class>))
- 在插件包對應的CMakeLists.txt中加入:
- find_package()中加入pluginlib
-
add_library(<plugins_lib_name> src/xxx.cpp)
- 在插件包目錄中增加一個名為_plugin.xml檔案,檔案内容如下:
<library path="lib/lib<plugins_lib_name>"> <class name="<pkg_name>/<plugin_name>" type="<plugin_namespace::plugin_class>" base_class_type="<baseClass_namespace::base_class>"> <description> This is a xxx plugin. </description> </class> </library>
- 在插件包的package.xml檔案中增加
<build_depend>pluginlib</build_depend> <run_depend>pluginlib</run_depend> <export> <<base_class_pkg_name> plugin="${prefix}/<plugins_lib_name>_plugin.xml"/> </export>
插件的加載與調用
- 在使用插件的代碼前include兩個頭檔案
- pluginlib/class_loader.h
- 定義了插件的base_class的頭檔案
- 定義一個插件的加載器:
pluginlib::ClassLoader<<base_class_namespace::base_class>> <loader_name>("<base_class_pkg_name>", "<base_class_namespace::base_class>")
- 使用ClassLoader的
函數加載具體插件,該函數傳回一個插件指針,通常将其指派給一個智能指針boost::shared_ptr,以便後續調用插件功能。createInstance("<plugin_namespace::plugin_class>")
- 上述第3步往往放在try中執行,并通過catch (const pluginlib::PluginlibException& ex)捕獲異常,并列印ex.what()以顯示發生的異常(如果有異常的話)
- 假設第3步中的createInstance傳回的指針為ptr,通過
可以調用插件中的函數ptr-><function>