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);
};