天天看點

ROS2學習之旅(17)——ROS2建立接口方式拓展(接口在包内)

ROS2學習之旅(17)——ROS2建立接口方式拓展(接口在包内)

ROS2在一個包内自定義消息類型...

基于上篇簡單建立自定義消息的文章,本文對建立接口方式進行拓展。

雖然最好的做法是是在專用接口包中聲明接口,但有時在一個包中聲明、建立和使用接口也很友善。

接口目前隻能在CMake包中定義。然而,在CMake包中有Python庫和節點是可能的(使用

ament_cmake_python

),是以可以在一個包中定義接口和Python節點。為了簡單起見,b本文使用CMake包和C++節點。

本問将側重于msg接口類型,但是這裡的步驟适用于所有接口類型。

1.建立功能包

在工作空間

src

目錄下,建立

more_interfaces

以及

msg

檔案夾:

ros2 pkg create --build-type ament_cmake more_interfaces
mkdir more_interfaces/msg
           

2.建立一個msg檔案

more_interfaces/msg

檔案夾下,建立一個新檔案:

AddressBook.msg

粘貼以下代碼來建立個人資訊:

bool FEMALE=true
bool MALE=false

string first_name
string last_name
bool gender
uint8 age
string address
           

該消息分為5個部分:

  • first_name: string 類型
  • last_name: string 類型
  • gender: bool 類型
  • age: uint8類型
  • address: string 類型

請注意,可以在消息定義中為字段設定預設值。更多ROS2接口的用法可以參考:[ROS2 interfaces](About ROS 2 interfaces — ROS 2 Documentation: Foxy documentation)。

2.1編譯msg檔案

打開

package.xml

檔案,添加:

<buildtool_depend>rosidl_default_generators</buildtool_depend>

<exec_depend>rosidl_default_runtime</exec_depend>

<member_of_group>rosidl_interface_packages</member_of_group>
           

注意:在編譯時,需要

rosidl_default_generators

,而在運作時,隻需要

rosidl_default_runtime

CMakeLists.txt

檔案,并添加以下代碼:

找到從

msg/srv

檔案中生成消息代碼的包:

find_package(rosidl_default_generators REQUIRED)
           

聲明要生成的消息清單:

set(msg_files
  "msg/AddressBook.msg"
)
           

通過手動添加

.msg

檔案,可以確定CMake知道在添加其他

.msg

檔案後,它何時需要重新配置項目。

生成消息:

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
)
           

還要確定導出消息運作時的依賴項:

ament_export_dependencies(rosidl_default_runtime)
           

2.2設定多個接口

可以使用

set

整潔的來列出所有的接口:

set(msg_files
  "msg/Message1.msg"
  "msg/Message2.msg"
  # etc
  )

set(srv_files
  "srv/Service1.srv"
  "srv/Service2.srv"
   # etc
  )
           

然後像這樣一次生成所有消息:

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  ${srv_files}
)
           

3.在同一個包裡使用接口

more_interfaces/src

目錄下,建立一個名為

publish_address_book.cpp

的檔案并粘貼以下代碼:

#include <chrono>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "more_interfaces/msg/address_book.hpp"

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public:
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher<more_interfaces::msg::AddressBook>("address_book", 10);

    auto publish_msg = [this]() -> void {
        auto message = more_interfaces::msg::AddressBook();

        message.first_name = "John";
        message.last_name = "Doe";
        message.age = 30;
        message.gender = message.MALE;
        message.address = "unknown";

        std::cout << "Publishing Contact\nFirst:" << message.first_name <<
          "  Last:" << message.last_name << std::endl;

        this->address_book_publisher_->publish(message);
      };
    timer_ = this->create_wall_timer(1s, publish_msg);
  }

private:
  rclcpp::Publisher<more_interfaces::msg::AddressBook>::SharedPtr address_book_publisher_;
  rclcpp::TimerBase::SharedPtr timer_;
};


int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  rclcpp::spin(std::make_shared<AddressBookPublisher>());
  rclcpp::shutdown();

  return 0;
}
           

3.1代碼解釋

#include "more_interfaces/msg/address_book.hpp"
           

包含建立的

AddressBook.msg

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public:
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher<more_interfaces::msg::AddressBook>("address_book");
           

建立一個名為

address_book_publisher

的釋出者。

auto publish_msg = [this]() -> void {
           

建立回調以定期釋出消息。

auto message = more_interfaces::msg::AddressBook();
           

建立稍後将釋出的AddressBook消息執行個體。

message.first_name = "John";
message.last_name = "Doe";
message.age = 30;
message.gender = message.MALE;
message.address = "unknown";
           

填充AddressBook字段。

std::cout << "Publishing Contact\nFirst:" << message.first_name <<
  "  Last:" << message.last_name << std::endl;

this->address_book_publisher_->publish(message);
           

最後定期發送消息。

timer_ = this->create_wall_timer(1s, publish_msg);
           

建立一個1秒的計時器,每秒調用

publish_msg

函數。

3.2編譯釋出者

需要在

CMakeLists.txt

中添加:

find_package(rclcpp REQUIRED)

add_executable(publish_address_book
  src/publish_address_book.cpp
)

ament_target_dependencies(publish_address_book
  "rclcpp"
)

install(TARGETS publish_address_book
 DESTINATION lib/${PROJECT_NAME})
           

3.3對接接口

為了使用在同一個包中生成的消息,需要使用以下CMake代碼:

rosidl_target_interfaces(publish_address_book
  ${PROJECT_NAME} "rosidl_typesupport_cpp")
           

這将找到

AddressBook.msg

中生成的相關c++代碼,并且允許連結到它。

當使用的接口來自單獨建構的包時,此步驟是不必要的。這個CMake代碼隻有當你想要在同一個包中使用接口時才需要。

4.運作

傳回工作空間根目錄,編譯功能包:

cd ~/dev_ws
colcon build --packages-up-to more_interfaces
           

然後運作:

. install/local_setup.bash
ros2 run more_interfaces publish_address_book
           

此時會看到釋出者轉發自定義的msg,包括在

publish_address_book.cpp

中設定的值。

要确認消息釋出在

address_book

話題上,打開另一個終端(不要忘記source),并調用

topic echo

:

. install/setup.bash
ros2 topic echo /address_book
           

5.使用現有的接口定義

可以在新接口定義中使用現有接口定義。例如,假設有一個名為

Contact.msg

的消息,它屬于一個現有的名為

rosidl_tutorials_msgs

的ROS 2包,假設它的定義與定制的

AddressBook.msg

相同。

在這種情況下,可以定義

AddressBook.msg

(一個在節點包中的接口)類型為

Contact

(一個單獨包中的接口)。甚至可以定義

AddressBook.msg

作為

Contact

類型的數組,如下所示:

rosidl_tutorials_msgs/Contact[] address_book
           

要生成此消息,需要聲明對

Contact.msg

包的依賴關系,

rosidl_tutorials_msgs

,在

package.xml

添加:

<build_depend>rosidl_tutorials_msgs</build_depend>

<exec_depend>rosidl_tutorials_msgs</exec_depend>
           

CMakeLists.txt

find_package(rosidl_tutorials_msgs REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
  DEPENDENCIES rosidl_tutorials_msgs
)
           

還需要包含

Contact.msg

的頭檔案:

#include "rosidl_tutorials_msgs/msg/contact.hpp"
           

可以把這個調用改成這樣:

auto publish_msg = [this]() -> void {
   auto msg = std::make_shared<more_interfaces::msg::AddressBook>();
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "John";
     contact.last_name = "Doe";
     contact.age = 30;
     contact.gender = contact.MALE;
     contact.address = "unknown";
     msg->address_book.push_back(contact);
   }
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "Jane";
     contact.last_name = "Doe";
     contact.age = 20;
     contact.gender = contact.FEMALE;
     contact.address = "unknown";
     msg->address_book.push_back(contact);
   }

   std::cout << "Publishing address book:" << std::endl;
   for (auto contact : msg->address_book) {
     std::cout << "First:" << contact.first_name << "  Last:" << contact.last_name <<
       std::endl;
   }

   address_book_publisher_->publish(*msg);
 };
           

6.總結