ros2 学习笔记1 -- 实现自定义接口(implmenting custom interfaces)

1.背景

之前的已经学习如何创建自定义的msg和srv接口

虽然最好的做法是在专门的接口包里进行声明,但有时在一个包里进行声明,创建和使用接口会很方便,回顾一下,目前接口只能在Cmake包中进行定义,然而,在Cmake包中含有python库和节点是可能的(使用ament_cmake_python)所以可以在一个包里一起定义接口和python节点,我们这里将使用一个Cmake和C++节点

2.步骤

2.1创建包

在/dev_ws/src目录下,创建一个more_interfface的包和创建一个msg的文件

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

2.2创建msg文件

在more_interfaces/msg下创建一个AddressBook.msg,

touch AddressBook.msg

写入以下代码

uint8 PHONE_TYPE_HOME=0
uint8 PHONE_TYPE_WORK=1
uint8 PHONE_TYPE_MOBILE=2

string first_name
string last_name
string phone_number
uint8  phone_type

该消息由以下字段组成

  • first_name: of type string

  • last_name: of type string

  • phone_number: of type string

  • phone_type: of type uint8, with several named constant values defined

请注意,可以为消息定义中的字段设置默认值。有关自定义界面的更多方法,请参阅界面:Interfaces — ROS 2 Documentation: Humble documentation

接下来,我们要确保msg文件转换为c++,python和其他语言的源文件

2.3 编译msg文件

打开package.xml 添加以下内容:

rosidl_default_generators

rosidl_default_runtime

rosidl_default_runtime

注意到,在编译时,我们需要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(${PRIJECT_NAME}
        ${msg_files}
)

还要确保导出消息运行时依赖项:

ament_export_dependencies(rosidl_default_runtime)

现在您已准备好根据您的消息定义生成源文件。现在将跳过编译步骤,将在下面的步骤 4 中一起完成所有步骤

2.4 设置多种接口

可以使用CmakeLists.txt中的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.使用同一个包里的接口

3.1 publish_address_book.cpp

在more_interfaces/src下创建一个publish_address_book.cpp文件,复制一下代码

#include 
#include 

#include "rclcpp/rclcpp.hpp"
#include "more_interfaces/msg/address_book.hpp" //包含我们创建的msg文件

using namespace std::chrono_literals;

class AddressBookPublisher : public rclcpp::Node
{
public: 
  AddressBookPublisher()
  : Node("address_book_publisher")
  {
    address_book_publisher_ =
      this->create_publisher("address_book", 10);
      //生成一个节点address_book_publisher以及话题发布者 AddressBookPublisher

    auto publish_msg = [this]() -> void {
        auto message = more_interfaces::msg::AddressBook();//创建一个消息 AddressBook稍后发送

        message.first_name = "John";
        message.last_name = "Doe";
        message.phone_number = "1234567890";
        message.phone_type = message.PHONE_TYPE_MOBILE;
          //定期发送消息
        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);//创建一个定时器,每秒调用一次publish_msg
  }

private:
  rclcpp::Publisher::SharedPtr address_book_publisher_;
  //声明了一个指向 rclcpp::Publisher 类模板实例的共享指针 ,用于将消息发布到主题。
  //more_interfaces::msg::AddressBook 模板参数指定将要发布的消息类型。SharedPtr 类型是一个智能指针,提供了对象的共享所有权。
  rclcpp::TimerBase::SharedPtr timer_;
  //声明一个指向rclcpp::TimerBase类的共享指针,用于创建定时器,指定时间间隔后调用回调函数。
};


int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);//初始化
  rclcpp::spin(std::make_shared());//运行
  rclcpp::shutdown();//清理ros2客户端库以及中间件资源

  return 0;
}

3.2 编译publisher

我们需要在CmakeLists.txt中为此节点创建一个新目标:

find_package(rclcpp  REQUIRED)

add_exexutable(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_get_typesupport_target(cpp_typesupport_target
    ${PROJECT_NAME} rosidl_typesupport_cpp)

target_link_libraries(publish_address_book "${cpp_typesupport_target}")

这会从 AddressBook.msg 找到相关生成的 C++ 代码,并允许您的目标链接到它。

3.4 subcriber_address_book.cpp

仿造上面创建subsriber_address_book.cpp文件,创建一个订阅者话题,接收发布者发布的话题

#include 

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

class AddressBookSubscriber : public rclcpp::Node
{
public:
  AddressBookSubscriber()
  : Node("address_book_subscriber")
  {
    address_book_subscriber_ = this->create_subscription(
      "address_book", 10, std::bind(&AddressBookSubscriber::topic_callback, this, std::placeholders::_1));
  }
/*
create_subscription是ROS2中的一个函数,它可以创建一个订阅者对象。
在这个例子中,this->create_subscription("address_book", 10,
创建了一个订阅者对象,它订阅了名为"address_book"的主题,并且每次接收到消息时,都会调用一个回调函数。
在ROS2中,create_subscription函数的第二个参数是订阅者队列的大小。这个参数指定了订阅者队列中可以缓存
的消息数量。如果订阅者不能及时处理消息,那么这些消息将被缓存到队列中,直到队列被填满。在这个例子中,10指定了订阅者队列的大小为10
*/
/*
std::bind是一个函数模板,它可以将一个函数对象和一些参数绑定在一起,返回一个新的函数对象。这个新的函数对象可以像原来的函数对象一样调用,
但是它的参数已经被绑定了。在这个例子中,std::bind(&AddressBookSubscriber::topic_callback, this, std::placeholders::_1)
将AddressBookSubscriber::topic_callback函数和this指针绑定在一起,并将第一个参数绑定到占位符std::placeholders::_1上。
当新的函数对象被调用时,它将调用AddressBookSubscriber::topic_callback(this, arg1)
*/
private:
  void topic_callback(const more_interfaces::msg::AddressBook::SharedPtr msg) const
  {             
    std::cout << "I heard:\nFirst:" << msg->first_name <<
      "  Last:" << msg->last_name << "\nPhone:" << msg->phone_number <<
      "\nPhone Type:" << msg->phone_type << std::endl;
  }//将函数消息打印到控制台

  rclcpp::Subscription::SharedPtr address_book_subscriber_;
    /*声明了一个名为 address_book_subscriber_ 的共享指针,
    该指针指向一个 rclcpp::Subscription 类模板实例。
    这个实例代表了一个订阅者对象,它订阅了名为 “address_book” 的主题,并且每次接收到消息时,都会调用一个回调函数。*/

};

int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);//初始化
  rclcpp::spin(std::make_shared());
  /*std::make_shared() 
  创建了一个 AddressBookSubscriber 实例,并将其传递给 rclcpp::spin() 函数,以便在 ROS 2 系统中运行该实例。*/
  rclcpp::shutdown();

  return 0;
}

由于该节点与发布者节点具有相同的依赖关系,因此无需向 package.xml 添加任何新内容

需要在CmakeLists.txt添加以下内容:

add_executable(subcriber_address_book  src/subcriber_address_book.cpp)
ament_target_dependencies(subcriber_address_book rclcpp)

install(TARGETS
    subcriber_address_book
  DESTINATION lib/${PROJECT_NAME})
#将subcriber_address_book两个可执行文件安装到lib/more_interfaces目录下


target_link_libraries(subcriber_address_book "${cpp_typesupport_target}")

完整的CmakeLists.txt 如下:

cmake_minimum_required(VERSION 3.8)
project(more_interfaces)

if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
  add_compile_options(-Wall -Wextra -Wpedantic)
endif()


find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(rclcpp REQUIRED)


set(msg_files
  "msg/AddressBook.msg"
)

rosidl_generate_interfaces(${PROJECT_NAME}
  ${msg_files}
)

ament_export_dependencies(rosidl_default_runtime)



add_executable(publish_address_book    src/publish_address_book.cpp)
add_executable(subcriber_address_book  src/subcriber_address_book.cpp)
ament_target_dependencies(publish_address_book rclcpp)
ament_target_dependencies(subcriber_address_book rclcpp)

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


rosidl_get_typesupport_target(cpp_typesupport_target  ${PROJECT_NAME} rosidl_typesupport_cpp)

target_link_libraries(publish_address_book "${cpp_typesupport_target}")
target_link_libraries(subcriber_address_book "${cpp_typesupport_target}")


ament_package()

4.编译包

返回工作区根目录来编译包:

cd  ~dev_ws
colcon build

然后获取工作区并运行publisher:

source install/local_setup.sh
ros2 run more_interfaces publisher_address_book

单独开一个终端运行:

ros2 run more_interfaces subcriber_address_book

可以看到publisher发布的信息 

ros2 学习笔记1 -- 实现自定义接口(implmenting custom interfaces)_第1张图片

订阅者接收到的信息

ros2 学习笔记1 -- 实现自定义接口(implmenting custom interfaces)_第2张图片

 5 使用现有定义的将接口

 例如,假设有一个名为Contact.msg的消息,属于一个现有的ROS 2包,名为rosidl_tutorials_msgs。假设它的定义与我们前面定制的AddressBook.msg接口相同。
在这种情况下,您可以将AddressBook.msg(有你的节点的包中的一个接口)定义为Contact(单独包中的一个接口)类型。你甚至可以将AddressBook.msg定义为一个Contact类型的数组,像这样:

rosidl_tutorials_msgs/Contact[] address_book

为了生成message,需要在package.xml中声明对Contact.msg包rosidl_tutorials_maga的依赖关系

rosidl_tutorials_msgs

rosidl_tutorials_msgs

并且在CmakeLists.txt增加:

find_package(rosidl_tutorials_msgs REQUIRED)

rosidl_generate_interfaces(${PRIJECT_NAME}
    ${msg_files}
     DEPENDENCIES rosidl_tutorials_mags
)

为了能够使用该Contact类型的消息,你需要引用改头文件:

#include "rosidl_tutorials_msgs/msg/contact.hpp"

可以将回调更改为如下所示:

auto publish_msg = [this]() -> void {
   auto msg = std::make_shared();
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "John";
     contact.last_name = "Doe";
     contact.phone_number = "1234567890";
     contact.phone_type = message.PHONE_TYPE_MOBILE;
     msg->address_book.push_back(contact);
   }
   {
     rosidl_tutorials_msgs::msg::Contact contact;
     contact.first_name = "Jane";
     contact.last_name = "Doe";
     contact.phone_number = "4254242424";
     contact.phone_type = message.PHONE_TYPE_HOME;
     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);
 };

 编译和运行更改后(的源码),会表现出所预期的消息类型msg,和上面所定义的消息msg阵列.

6. 总结

尝试了不同接口的定义,然后构建了一个接口和使用都在同一个包运行的方式,也学会了构建接口时,package.xml, CMakeLists.txt,#include声明(如何写)来使用

你可能感兴趣的:(ROS2,学习,笔记,c++)