ROS 2: カスタムメッセージ の作成と他パッケージでの使用
海洋ロボコンをやってた人です。
今回はROS 2 カスタムメッセージ の作成と他パッケージでの使用について、久しぶりに実装したら忘れていたので、その手順を備忘録として記載しておきます。
特にCPPでのカスタムメッセージのサンプルとサマリが一目でわかる資料が欲しかったのが経緯です。
サンプルコードも置いてあるので、動かしながら理解していただければ幸いです。
以下箇条書きになりますが、何卒宜しくお願い致します。
また、誤記等あればご指摘ください。
必要に応じて、適宜追加します。
1. Custom Msg の作成と他パッケージの利用
1.1 Custom Msgの作成
以下のディレクトリ構造となるようにパッケージを作成します。
ros2 pkg create --build-type ament_cmake pos_array_msgs --dependencies rclcpp
include, srcは不要なので削除してください。
続いて、作成したいカスタムメッセージを記述します。カスタムメッセージは
パスカルケース: PascalCaseでmsgファイルを作成してください。
int32 id # Pos Array ID
geometry_msgs/Point[] positions # Pos Array (x, y, z)
また、カスタムメッセージの依存関係は以下のように記述します。
cmake_minimum_required(VERSION 3.5)
project(pos_array_msgs)
set(CMAKE_CXX_STANDARD 14)
if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang")
add_compile_options(-Wall -Wextra -Wpedantic)
endif()
# find dependencies
find_package(ament_cmake REQUIRED)
find_package(geometry_msgs REQUIRED)
find_package(rosidl_default_generators REQUIRED)
# Generate messages and services
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/PosArray.msg"
"srv/SetPosition.srv"
DEPENDENCIES geometry_msgs
)
ament_export_dependencies(rosidl_default_runtime)
ament_package()
<?xml version="1.0"?>
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>pos_array_msgs</name>
<version>0.1.0</version>
<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<depend>geometry_msgs</depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
<!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here -->
<build_type>ament_cmake</build_type>
</export>
</package>
1.2 Custom Msgを他のパッケージで使用する
他パッケージでの使用サマリ
【.hpp / .cpp】
インクルード時はスネークケース: snake_caseで記述すること
#include "pos_array_msgs/msg/pos_array.hpp"
のようにパッケージ名/msg/スネークケース名.msg とする。
PosArray.msg → pos_array.hpp
【CMakeLists.txt】
find_package(pos_array_msgs REQUIRED)
ament_target_dependencies()
にカスタムメッセージのスネークケース名を追加する(pos_array_msgs)
【package.xml】
<depend>pos_array_msgs</depend>
を追加する
実装例
#include "rclcpp/rclcpp.hpp"
#include "pos_array_msgs/msg/pos_array.hpp"
#include "geometry_msgs/msg/point.hpp"
using namespace std::chrono_literals;
class PosArrayPublisher : public rclcpp::Node
{
public:
/**
* @brief Constructor for the PosArrayPublisher class.
* Initializes the node, creates publishers for four different topics, and sets up timers to periodically publish messages.
*/
PosArrayPublisher()
: Node("pos_array_publisher")
{
// Create publishers for different topics
publisher_1_ = this->create_publisher<pos_array_msgs::msg::PosArray>("pos_array_topic_1", 10);
publisher_2_ = this->create_publisher<pos_array_msgs::msg::PosArray>("pos_array_topic_2", 10);
publisher_3_ = this->create_publisher<pos_array_msgs::msg::PosArray>("pos_array_topic_3", 10);
publisher_4_ = this->create_publisher<pos_array_msgs::msg::PosArray>("pos_array_topic_4", 10);
// Set up timers to periodically call the publish_message function for each publisher
timer_1_ = this->create_wall_timer(
1s, [this]() { this->publish_message(publisher_1_, 1); });
timer_2_ = this->create_wall_timer(
1s, [this]() { this->publish_message(publisher_2_, 2); });
timer_3_ = this->create_wall_timer(
1s, [this]() { this->publish_message(publisher_3_, 3); });
timer_4_ = this->create_wall_timer(
1s, [this]() { this->publish_message(publisher_4_, 4); });
}
private:
/**
* @brief Publishes a message with a specific ID to a given publisher.
* Fills the message with data based on the ID and publishes it to the associated topic.
*
* @param publisher The publisher to send the message to.
* @param id The ID to set in the message and determine the data content.
*/
void publish_message(rclcpp::Publisher<pos_array_msgs::msg::PosArray>::SharedPtr publisher, int id)
{
auto message = pos_array_msgs::msg::PosArray();
message.id = id;
// Set different data based on the ID
if (id == 1) {
for (int i = 0; i < 3; ++i) {
geometry_msgs::msg::Point point;
point.x = static_cast<double>(i);
point.y = static_cast<double>(i * 2);
point.z = static_cast<double>(i * 3);
message.positions.push_back(point);
}
} else if (id == 2) {
for (int i = 0; i < 3; ++i) {
geometry_msgs::msg::Point point;
point.x = static_cast<double>(i * 2);
point.y = static_cast<double>(i * 4);
point.z = static_cast<double>(i * 6);
message.positions.push_back(point);
}
} else if (id == 3) {
for (int i = 0; i < 3; ++i) {
geometry_msgs::msg::Point point;
point.x = static_cast<double>(i * 3);
point.y = static_cast<double>(i * 6);
point.z = static_cast<double>(i * 9);
message.positions.push_back(point);
}
} else if (id == 4) {
for (int i = 0; i < 3; ++i) {
geometry_msgs::msg::Point point;
point.x = static_cast<double>(i * 4);
point.y = static_cast<double>(i * 8);
point.z = static_cast<double>(i * 12);
message.positions.push_back(point);
}
}
RCLCPP_INFO(this->get_logger(), "Publishing ID: '%d' on topic '%s'", message.id, publisher->get_topic_name());
for (const auto & pos : message.positions) {
RCLCPP_INFO(this->get_logger(), "Position: [x: %f, y: %f, z: %f]", pos.x, pos.y, pos.z);
}
publisher->publish(message);
}
rclcpp::Publisher<pos_array_msgs::msg::PosArray>::SharedPtr publisher_1_; ///< Publisher for topic 1
rclcpp::Publisher<pos_array_msgs::msg::PosArray>::SharedPtr publisher_2_; ///< Publisher for topic 2
rclcpp::Publisher<pos_array_msgs::msg::PosArray>::SharedPtr publisher_3_; ///< Publisher for topic 3
rclcpp::Publisher<pos_array_msgs::msg::PosArray>::SharedPtr publisher_4_; ///< Publisher for topic 4
rclcpp::TimerBase::SharedPtr timer_1_; ///< Timer for periodically publishing messages on topic 1
rclcpp::TimerBase::SharedPtr timer_2_; ///< Timer for periodically publishing messages on topic 2
rclcpp::TimerBase::SharedPtr timer_3_; ///< Timer for periodically publishing messages on topic 3
rclcpp::TimerBase::SharedPtr timer_4_; ///< Timer for periodically publishing messages on topic 4
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
// Create and spin the PosArrayPublisher node
rclcpp::spin(std::make_shared<PosArrayPublisher>());
rclcpp::shutdown();
return 0;
}
cmake_minimum_required(VERSION 3.5)
project(pos_array_ros)
# Default to C++17
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Find packages
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(pos_array_msgs REQUIRED)
find_package(geometry_msgs REQUIRED)
# Declare the executable
add_executable(pos_array_publisher src/pos_array_publisher.cpp)
# Specify libraries to link the executable against
ament_target_dependencies(pos_array_publisher
rclcpp
pos_array_msgs
geometry_msgs
)
add_executable(set_position_server src/set_position_server.cpp)
ament_target_dependencies(set_position_server
rclcpp
pos_array_msgs
)
add_executable(set_position_client src/set_position_client.cpp)
ament_target_dependencies(set_position_client
rclcpp
pos_array_msgs
)
# Install the executable
install(TARGETS
pos_array_publisher
set_position_server
set_position_client
DESTINATION lib/${PROJECT_NAME})
# Macro to register the package with ament
ament_package()
find_package(pos_array_msgs REQUIRED)
ament_target_dependencies()
にカスタムメッセージのスネークケース名を追加する(今回ならpos_array_msgs)
<?xml version="1.0"?>
<package format="2">
<name>pos_array_ros</name>
<version>0.0.0</version>
<description>ROS samples for custom PosArray messages</description>
<license>Apache-2.0</license>
<depend>rclcpp</depend>
<!-- add custom msgs depended -->
<depend>pos_array_msgs</depend>
<depend>geometry_msgs</depend>
<buildtool_depend>ament_cmake</buildtool_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
<depend>pos_array_msgs</depend>
を追加する
srvの説明や、他にもカスタムメッセージについて書きたい内容は別途追加予定としておきます。(T.B.D)
Reference
以下のmsgsファイルの書き方などは参考になると思います。
以上です。
Likeいただけると大変励みになりますので、よろしくお願いいたします。
Discussion