天天看點

ROS包全攻略:消息vs服務;可執行vs插件

ROS包通過catkin_create_pkg建立,是實作系統的基本單元。根據對資訊的傳遞和處理方式,ROS包可以分為消息模式和服務模式;根據編譯後的調用方法,又可分為可執行包和插件包。ROS包的實作大多在上述的分類範圍内。

1、ROS包消息/服務模式與要點

從功能上看,ROS包是資訊互動和處理的基本單元。根據資訊的互動和處理方式,ROS包有以下兩大類:

  1. 消息釋出者與訂閱者
  2. 伺服器與用戶端

對于消息模式的包,資訊的提供者主動,資訊的使用者被動:資訊使用者接收到消息後執行回調函數處理資訊,即處理資訊的回調函數由資訊釋出者觸發;

對于服務模式的包,資訊的提供者被動,資訊的使用者主動:資訊使用者需要時向資訊提供者查詢,提供者收到查詢參數後,調用回調函數計算結果然後給予應答,即處理資訊的回調函數由服務的查詢者(資訊使用者)觸發;

要點如下:

1.1 消息釋出與訂閱

消息的定義方法:

  1. 消息定義于包目錄的msg/xxx.msg中
  2. 定義消息的包可以與釋出者/訂閱者的包不一樣
  3. 在消息定義包的package.xml中加上build_depend和run_depend限制項
  4. 在消息定義包的CMakeLists.txt中設定好find_package(), catkin_package(), add_message_files(), generate_messages()

消息的釋出

  1. 完成節點初始化 ros::init()
  2. 建立節點句柄 ros::NodeHandle
  3. 調用NodeHandle的advertise方法建立消息釋出者
  4. 調用消息釋出者的publish方法釋出消息

消息的訂閱

  1. 建立訂閱到消息後需要執行的回調函數
  2. 完成節點初始化 ros::init()
  3. 建立節點句柄 ros::NodeHandle
  4. 調用NodeHandle的subscribe方法建立消息訂閱者并綁定回調函數
  5. 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 伺服器與用戶端

服務的定義方法:

  1. 服務定義于包目錄的srv/xxx.srv中
  2. 定義服務的包可以與伺服器/用戶端的包不一樣
  3. 在消息定義包的CMakeLists.txt中設定好find_package(), add_service_files(), generate_messages()

伺服器

  1. 建立處理服務資料的回調函數
  2. 完成節點初始化 ros::init()
  3. 建立節點句柄 ros::NodeHandle
  4. 調用節點句柄的advertiseService函數啟動服務
  5. ros::spin()

用戶端

  1. 完成節點初始化 ros::init()
  2. 建立節點句柄 ros::NodeHandle
  3. 調用節點句柄的serviceClient函數建立用戶端對象client,通過服務名稱綁定服務
  4. 申明服務類型的對象(srv),并将srv.request部分賦初值
  5. 調用client的call(srv)函數調用服務
  6. 服務執行成功後,通過srv.response取得伺服器的應答結果

指令行工具

  1. rosservice call <service-name> [service-args]

    調用特定服務

1.3 需要注意的細節

  1. 消息/服務模式的節點實作時要include消息/服務對應的頭檔案
  2. 上述頭檔案需要先編譯消息定義包來生成,是以需要在消息/服務使用者對應的CMakeLists.txt中加上add_dependencies()限制,否則會出現找不到頭檔案的現象
  3. 資訊互動中的被動方,即消息模式的消息訂閱者或服務模式的伺服器在都要調用ros::spin(),該函數確定回調能夠發生。

2、可執行包/插件包及要點

剛剛從資訊互動和處理角度介紹了ROS包的兩種模式,不管是哪種模式的ROS包,根據編譯後調用的方式,都可以分為以下兩種情況:

  1. 可執行包
  2. 插件包

可執行包可以通過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 插件包

插件的定義方法

  1. 定義一個基類,所有的插件将繼承這一基類。在該基類中以公有成員函數(虛函數)的形式定義插件所具備的共同的接口。基類放到一個.h檔案中,該檔案可以與具體插件的實作不在一個ROS包中
  2. 定義具體的插件類,繼承上述基類,放到.h檔案中
  3. 在.cpp檔案中實作插件類的具體功能,并在該.cpp檔案中額外加上兩行:
    #include <pluginlib/class_list_macros.h>
    
    PLUGINLIB_EXPORT_CLASS(<plugin_namespace::plugin_class>, <baseClass_namespace::base_class>))
               
  4. 在插件包對應的CMakeLists.txt中加入:
    • find_package()中加入pluginlib
    • add_library(<plugins_lib_name> src/xxx.cpp)

  5. 在插件包目錄中增加一個名為_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>
               
  6. 在插件包的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>
               

插件的加載與調用

  1. 在使用插件的代碼前include兩個頭檔案
    • pluginlib/class_loader.h
    • 定義了插件的base_class的頭檔案
  2. 定義一個插件的加載器:
    pluginlib::ClassLoader<<base_class_namespace::base_class>> <loader_name>("<base_class_pkg_name>", "<base_class_namespace::base_class>") 
               
  3. 使用ClassLoader的

    createInstance("<plugin_namespace::plugin_class>")

    函數加載具體插件,該函數傳回一個插件指針,通常将其指派給一個智能指針boost::shared_ptr,以便後續調用插件功能。
  4. 上述第3步往往放在try中執行,并通過catch (const pluginlib::PluginlibException& ex)捕獲異常,并列印ex.what()以顯示發生的異常(如果有異常的話)
  5. 假設第3步中的createInstance傳回的指針為ptr,通過

    ptr-><function>

    可以調用插件中的函數

繼續閱讀