C++でのtopic通信の作り方を示します。
公式のC++のトピックはこちらに説明があります。
はじめにデータを送信するpublisherというのものを作ります。
まずは下記コマンドで新しくパッケージを作ります。
一応テストなので~/ros2_ws/src/cpp_pkgというディレクトリを作っておきます。
$ mkdir -p ~/ros2_ws/src/cpp_pkg
$ cd ~/ros2_ws/src/cpp_pkg
$ ros2 pkg create --build-type ament_cmake --node-name publisher_node mypubsub
--build-type でcmakeを使うので「ament_cmake」を指定します。
--node-nameで自分の好きなパッケージ名とノード名を書きます。最初にノード名、スペースを空けてからパッケージ名を記載します。
今回は最初にtopicを発信するノードを作るので、ノード名を「publisher_node」、パッケージ名を「mypubsub」としました。
mypubsubというディレクトリができているはずなので移動します。
$ cd mypubsub
$ ls
上記のlsを入力するとディレクトリ内のファイルとディレクトリの一覧が出てきます。
修正するのは、package.xmlとCMakeLists.txtとsrcディレクトリ内のpublisher_node.cppの3つです。
まず、setup.pyから修正しましょう。geditというテキストエディタを下記で使っていますが好きなエディタで修正してください。
$ gedit package.xml
下記のように修正します。修正するところはコメントのある3か所。別に修正しなくてもコンパイルはできる。
<?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>mypubsub</name>
<version>0.0.0</version>
<description>C++ Topic Sample</description> # パッケージの説明
<maintainer email="takubo@***.***">takubo</maintainer> #保守者の名前とメール
<license>Apache License 2.0</license> # ライセンスの形態
<buildtool_depend>ament_cmake</buildtool_depend>
<test_depend>ament_lint_auto</test_depend>
<test_depend>ament_lint_common</test_depend>
<export>
<build_type>ament_cmake</build_type>
</export>
</package>
次に、CMakeLists.txtを修正。修正箇所はコメントの場所。
cmake_minimum_required(VERSION 3.5)
project(mypubsub)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
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(rclcpp REQUIRED) # 追加
find_package(std_msgs REQUIRED) # 追加
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
add_executable(publisher_node src/publisher_node.cpp)
target_include_directories(publisher_node PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_target_dependencies(publisher_node rclcpp std_msgs) # 追加
install(TARGETS publisher_node
DESTINATION lib/${PROJECT_NAME})
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
# the following line skips the linter which checks for copyrights
# uncomment the line when a copyright and license is not present in all source files
#set(ament_cmake_copyright_FOUND TRUE)
# the following line skips cpplint (only works in a git repo)
# uncomment the line when this package is not in a git repo
#set(ament_cmake_cpplint_FOUND TRUE)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
次に、publisher_node.cppを編集します。
$ gedit ~/ros2_ws/src/cpp_pkg/mypubsub/src/publisher_node.cpp
書き換え後の内容は下記の通り。
//publihse_node.cppの内容
#include <chrono>
#include <memory>
#include "rclcpp/rclcpp.hpp" //標準C++ライブラリ
#include "std_msgs/msg/string.hpp" //ROS2の標準メッセージ
using namespace std::chrono_literals;
class MyPublisher : public rclcpp::Node
{
public:
MyPublisher()
: Node("my_publisher"), count_(0)//ノード名を書く
{
publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10); //トピック名を書く。今回はtopicという名前でトピック送信。
timer_ = this->create_wall_timer(
500ms, std::bind(&MyPublisher::timer_callback, this)); //500ms間隔でループの指定
}
private:
void timer_callback() // Callback
{
auto message = std_msgs::msg::String();
message.data = "Hello, world! " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str());
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MyPublisher>());
rclcpp::shutdown();
return 0;
}
書き終わって保存したらコンパイルして実行してみる。
$ cd ~/ros2_ws
$ colcon build
$ source ~/ros2_ws/install/setup.bash
$ ros2 run mypubsub publisher_node
最後の行を実行すると"Hello World: 数字"のメッセージが0.5秒ごとに表示されるはず。下記が送信しているウィンドの表示例。
[INFO] [1679294186.037870800] [my_publisher]: Publishing: 'Hello, world! 0'
[INFO] [1679294186.536009641] [my_publisher]: Publishing: 'Hello, world! 1'
[INFO] [1679294187.037650583] [my_publisher]: Publishing: 'Hello, world! 2'
[INFO] [1679294187.538112844] [my_publisher]: Publishing: 'Hello, world! 3'
[INFO] [1679294188.037999494] [my_publisher]: Publishing: 'Hello, world! 4'
[INFO] [1679294188.538299663] [my_publisher]: Publishing: 'Hello, world! 5'
[INFO] [1679294189.037832103] [my_publisher]: Publishing: 'Hello, world! 6'
[INFO] [1679294189.537930972] [my_publisher]: Publishing: 'Hello, world! 7'
[INFO] [1679294190.037825714] [my_publisher]: Publishing: 'Hello, world! 8'
これだけだと送信しているだけで情報が通信できているかわからないので、受信側のプログラムを作る。
Publisherが送信したデータを受け取るプログラムを同じパッケージ内に作ってみます。別パッケージでやる場合はPublisherと同じようにros2 pkg createで作成してください。
下記コマンドでPublisherで作ったパッケージmypubsubのsrcに移動してcppファイルを作ります。
$ cd ~/ros2_ws/src/cpp_pkg/mypubsub/src
$ gedit subscriber_node.cpp
subscriber_node.cppを下記のように記載します。
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
using std::placeholders::_1;
class MySubscriber : public rclcpp::Node //MySubscriberクラス
{
public:
MySubscriber()
: Node("my_subscriber")
{
subscription_ = this->create_subscription<std_msgs::msg::String>(
"topic", 10, std::bind(&MySubscriber::topic_callback, this, _1));//topicという名前のトピックを購読、バッファ10
}
private:
void topic_callback(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MySubscriber>());
rclcpp::shutdown();
return 0;
}
次に、CMakeLists.txtを修正。
$ cd ~/ros2_ws/src/cpp_pkg/
$ gedit CMakeLists.txt
下記が修正したCMakeLists.txtファイルの内容。追加と書いてある3行を書く。
cmake_minimum_required(VERSION 3.5)
project(mypubsub)
# Default to C99
if(NOT CMAKE_C_STANDARD)
set(CMAKE_C_STANDARD 99)
endif()
# Default to C++14
if(NOT CMAKE_CXX_STANDARD)
set(CMAKE_CXX_STANDARD 14)
endif()
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(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
add_executable(publisher_node src/publisher_node.cpp)
add_executable(subscriber_node src/subscriber_node.cpp) # 追加
target_include_directories(publisher_node PUBLIC
$<BUILD_INTERFACE:${CMAKE_CURRENT_SOURCE_DIR}/include>
$<INSTALL_INTERFACE:include>)
ament_target_dependencies(publisher_node rclcpp std_msgs)
ament_target_dependencies(subscriber_node rclcpp std_msgs) # 追加
install(TARGETS publisher_node subscriber_node
DESTINATION lib/${PROJECT_NAME})
#install(TARGETS subscriber_node DESTINATION lib/${PROJECT_NAME}) # 追加
if(BUILD_TESTING)
find_package(ament_lint_auto REQUIRED)
ament_lint_auto_find_test_dependencies()
endif()
ament_package()
下記を実行してコンパイル
$ cd ~/ros2_ws
$ colcon build
$ source ~/ros2_ws/install/setup.bash
$ ros2 run mypubsub subscrier_node
上記を実行すると、mypublisherで送信しているメッセージと同じメッセージが表示される。下記が受信したメッセージの表示例。
[INFO] [1679294202.037944086] [my_subscriber]: I heard: 'Hello, world! 32'
[INFO] [1679294202.538215138] [my_subscriber]: I heard: 'Hello, world! 33'
[INFO] [1679294203.037905060] [my_subscriber]: I heard: 'Hello, world! 34'
[INFO] [1679294203.539174977] [my_subscriber]: I heard: 'Hello, world! 35'
[INFO] [1679294204.038439260] [my_subscriber]: I heard: 'Hello, world! 36'
[INFO] [1679294204.538017998] [my_subscriber]: I heard: 'Hello, world! 37'
2つのターミナルからpublisher_nodeとsubscriber_nodeを別々に立ち上げるのは面倒なので、同時に立ち上げたいときは下記のリンクのlaunchファイルの作り方を参考にすること。
毎回colcon buildをやると全部のコンパイルを始めるので、下記コマンドで今作ったパッケージのみコンパイルすることができる。
cd ~/ros2_ws
colcon build --packages-select パッケージ名
source install/setup.bash