之前的已经学习如何创建自定义的msg和srv接口
虽然最好的做法是在专门的接口包里进行声明,但有时在一个包里进行声明,创建和使用接口会很方便,回顾一下,目前接口只能在Cmake包中进行定义,然而,在Cmake包中含有python库和节点是可能的(使用ament_cmake_python)所以可以在一个包里一起定义接口和python节点,我们这里将使用一个Cmake和C++节点
在/dev_ws/src目录下,创建一个more_interfface的包和创建一个msg的文件
ros2 pkg create --build-type ament_cmake more_interfaces
mkdir more_interface/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和其他语言的源文件
打开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 中一起完成所有步骤
可以使用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}
)
在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;
}
我们需要在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})
为了使用同一包中生成的消息,我们需要使用以下 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++ 代码,并允许您的目标链接到它。
仿造上面创建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()
返回工作区根目录来编译包:
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发布的信息
订阅者接收到的信息
例如,假设有一个名为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
阵列.
尝试了不同接口的定义,然后构建了一个接口和使用都在同一个包运行的方式,也学会了构建接口时,package.xml, CMakeLists.txt,
和#include
声明(如何写)来使用