[ROS2] Hello World
Intro
C++ 언어로 간단한 구조의 토픽(Topic) 퍼블리셔(Publisher)와 서브스크라이버(Subscriber)를 작성하고 동작시켜보자.
1. Package Directory
📦my_first_ros_rclcpp_pkg
┣ 📂include
┃ ┗ 📂my_first_ros_rclcpp_pkg
┣ 📂src
┃ ┣ 📜helloworld_publisher.cpp
┃ ┗ 📜helloworld_subscriber.cpp
┣ 📜CMakeLists.txt
┗ 📜package.xml
2. Code
2.1. CMakeLists.txt
# 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)
# Build
add_executable(helloworld_publisher src/helloworld_publisher.cpp)
ament_target_dependencies(helloworld_publisher rclcpp std_msgs)
add_executable(helloworld_subscriber src/helloworld_subscriber.cpp)
ament_target_dependencies(helloworld_subscriber rclcpp std_msgs)
# Install
install(TARGETS
helloworld_publisher
helloworld_subscriber
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()
기본 패키지의 CMakeLists.txt
파일에서 아래의 부분을 추가한 것이다.
# Build
add_executable(helloworld_publisher src/helloworld_publisher.cpp)
ament_target_dependencies(helloworld_publisher rclcpp std_msgs)
add_executable(helloworld_subscriber src/helloworld_subscriber.cpp)
ament_target_dependencies(helloworld_subscriber rclcpp std_msgs)
# Install
install(TARGETS
helloworld_publisher
helloworld_subscriber
DESTINATION lib/${PROJECT_NAME})
내용은 다음과 같이 정리할 수 있다.
src/helloworld_publisher.cpp
을 컴파일하여helloworld_publisher
실행파일 생성src/helloworld_subscriber.cpp
을 컴파일하여helloworld_subscriber
실행파일 생성- 실행파일
helloworld_publish
과helloworld_subscriber
은 패키지rclcpp
과std_msgs
에 의존성을 가짐 - 실행파일
helloworld_publish
과helloworld_subscriber
은 작업공간(workspace)의 디렉토리lib/${PROJECT_NAME}
에 저장
빌드를 완료해준 후에 디렉터리를 확인해보면 다음과 같음을 확인할 수 있다.
2.2. helloworld_publisher.cpp
/ Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include <iostream>
using namespace std::chrono_literals;
using namespace std;
class HelloworldPublisher : public rclcpp::Node
{
public:
HelloworldPublisher()
: Node("helloworld_publisher"), count_(0)
{
auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
helloworld_publisher_ = this->create_publisher<std_msgs::msg::String>(
"helloworld", qos_profile);
timer_ = this->create_wall_timer(
1s, std::bind(&HelloworldPublisher::publish_helloworld_msg, this));
}
private:
void publish_helloworld_msg()
{
auto msg = std_msgs::msg::String();
msg.data = "Hello World: " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Published message: '%s'", msg.data.c_str());
helloworld_publisher_->publish(msg);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr helloworld_publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
cout << "PUB: rclcpp::init(argc, argv);\n";
rclcpp::init(argc, argv);
auto node = std::make_shared<HelloworldPublisher>();
cout << "PUB: rclcpp::spin(node);\n";
rclcpp::spin(node);
cout << "PUB: rclcpp::shutdown();\n";
rclcpp::shutdown();
return 0;
}
퍼블리셔 노드의 cpp 내용을 정리하면 다음과 같다.
-
해당 노드의 메인 클래스는 rclcpp의 Node 클래스를 상속
class HelloworldPublisher : public rclcpp::Node
-
퍼블리셔 의 Qos에서 KeepLast 형태로 depth을 10으로 설정
auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
-
Node 클래스의 create_publisher 함수를 이용하여 퍼블리셔 설정. 매개변수의 토픽 메시지 타입은
String
, 토픽 이름은"helloworld"
, QoS는qos_profile
helloworld_publisher_ = this->create_publisher<std_msgs::msg::String>( "helloworld", qos_profile);
-
Node 클래스의 create_wall_timer 함수를 이용해 콜백 함수 실행. 주기는 1s.
timer_ = this->create_wall_timer( 1s, std::bind(&HelloworldPublisher::publish_helloworld_msg, this));
-
콜백 함수
void publish_helloworld_msg() { auto msg = std_msgs::msg::String(); msg.data = "Hello World: " + std::to_string(count_++); RCLCPP_INFO(this->get_logger(), "Published message: '%s'", msg.data.c_str()); helloworld_publisher_->publish(msg); }
2.3. helloworld_subscriber.cpp
서브스크라이브 함수도 퍼블리시 함수와 비슷하다.
// Copyright 2016 Open Source Robotics Foundation, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include <functional>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
#include <iostream>
using std::placeholders::_1;
using namespace std;
class HelloworldSubscriber : public rclcpp::Node
{
public:
HelloworldSubscriber()
: Node("Helloworld_subscriber")
{
auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
helloworld_subscriber_ = this->create_subscription<std_msgs::msg::String>(
"helloworld",
qos_profile,
std::bind(&HelloworldSubscriber::subscribe_topic_message, this, _1));
}
private:
void subscribe_topic_message(const std_msgs::msg::String::SharedPtr msg) const
{
RCLCPP_INFO(this->get_logger(), "Received message: '%s'", msg->data.c_str());
}
rclcpp::Subscription<std_msgs::msg::String>::SharedPtr helloworld_subscriber_;
};
int main(int argc, char * argv[])
{
cout << "SUB: rclcpp::init(argc, argv);\n";
rclcpp::init(argc, argv);
auto node = std::make_shared<HelloworldSubscriber>();
cout << "SUB: rclcpp::spin(node);\n";
rclcpp::spin(node);
cout << "SUB: rclcpp::shutdown();\n";
rclcpp::shutdown();
return 0;
}
3. Run
빌드를 진행한 뒤에 실행을 시켜보면 다음과 같다. 함수 호출 순서를 확인해 보기 위해서 cout
부분을 추가하였다.
rqt_graph
를 통해서 노드와 토픽을 살펴보자.