참고도서 1. ROS 2로 시작하는 로봇 프로그래밍. 표윤석 . 루비페이퍼 . 2021
참고도서 2. ROS2 혼자공부하는 로봇SW 직접 만들고 코딩하자 . 민형기 . 잇플 . 2022
참고자료 1. PinkWink(민형기)님의 ROS2 강의자료
참고자료 2. ROS2 humble documeation
ROS2를 복습하는 김에 제대로 다시 한 번 정리하고 넘어가고 싶어서 이 시리즈를 작성합니다.
이 글은 Turtlebot3 패키지를 제대로 이해하고, 다양한 센서를 부착하면서 활용하는 것을 목표로 합니다.
또한 글이 끝날 때 ROS와 관련된 문제를 하나 만들어서 직접 풀어보도록 합니다.
ROS Domain ID와 Namespace
이전 시간에 ROS 세상이라는 용어를 말한 적이 있습니다. ROS에서 발행하는 Topic들은 ROS 세상(ROS domain)을 둥둥 떠다니게 됩니다. 그래서 동일 네트워크를 다른 사람들과 공유하고 있다면 다른 사람들이 노드 정보에 쉽게 접근이 가능하여 관련 데이터를 공유할 수 있게 됩니다. 그러나 반대로 독립적인 작업을 해야하는 경우에는 이 기능이 불편할 수도 있습니다.
아마 제 블로그를 보고 ROS를 설치하셨다면 ROS 도메인을 이미 지정했을 것입니다.
gedit ~/.bashrc
bashrc에 들어가면 다음처럼 지정되어 있는 것을 확인할 수 있습니다.
alias humble="source /opt/ros/humble/setup.bash; ros_domain; echo \"ROS2 Humble is activated.\""
alias ros_domain="export ROS_DOMAIN_ID=33"
alias ros_study="humble; source ~/ros_study/install/local_setup.bash; echo \"ros_study workspace is activated.\""
alias sb="source ~/.bashrc; echo \"bashrc is reloaded.\""
ROS 도메인을 33번으로 설정한 것을 볼 수 있습니다. 같은 네트워크이면서 같은 ROS 도메인이라면 서로 소통이 가능하게됩니다.
Namespace는 토픽의 앞에 번호를 붙여주는 방식으로 제가 아는 것으로는 여러 대의 로봇을 제어해야하는 경우에 토픽이 겹치거나 하는 불상사를 방지하기 위해 사용합니다. 그래서 turtlesim의 경우 다음처럼 명렁어를 선언하면 됩니다.
ros2 run turtlesim turtlesim_node --ros-args -r __ns:=/robot1
다음처럼 입력하면
topic의 이름 앞에 robot1이 붙은 것을 확인할 수 있습니다. 이에 대한 이야기는 나중으로 미루도록 하고 다음으로 넘어가도록 합시다.
메시지 인터페이스 작성방법
우리가 지금만들었던 ros_study_ 패키지에서 인터페이스를 작성해도되지만 보통 의존성면의 관리 문제로 별도의 패키지에 모아서 사용을 한다.
그 이유를 생각해보면 내 로봇에 관련된 패키지에 인터페이스를 작성했다고 생각해봅시다. 만약 로봇을 시뮬레이션하는 패키지를 하나더 만들었다고 할때 로봇에 관련된 인터페이스를 가져오기 위해서는 로봇 패키지를 모두 가져와야합니다. 그러나 이를 분할하면 로봇 인터페이스 패키지만 가져오면 됩니다.
그러면 패키지를 하나 만드는 것으로 시작을 하도록 합시다.
ros2 pkg create --build-type ament_cmake ros_study_msgs
여기서 하나 신기한게 발견되어야 합니다. 메시지 패키지인데, 왜 cmake로 만드는가? ament_cmake에는 메시지를 include하거나 import 할 수 있게 하는 기능이 있지만 ament_python에는 이게 없는 등의 ament_cmake가 ament_python보다 더 많은 기능을 지원하고 있습니다.
그러면 이 패키지 안으로 들어가서 3개의 폴더를 만들어 주시면 됩니다.
다음처럼 만들면 준비는 끝납니다. 그 안에 파일을 3개 만들도록 합시다.
msgs, srv, msg 파일을 만들었습니다. 이 안에 메시지 파일을 작성해보도록 합시다.
주의할 점이라면 인터페이스 파일에는 작성 규정이 정해져 있습니다. 만약 이름은 위와 같이 한다면 빌드가 안됩니다. 즉, 첫 문자는 대문자이고 나머지 문자는 대/소문자와 숫자 조합이어야 합니다.
우선 MyMsg.msg 파일을 수정하겠습니다.
float32 num
이렇게 수정을 하면 됩니다.
그리고 srv같은 경우에는 다음처럼 작성할 수 있습니다. MySrv.srv
# Request
float32 req
---
# Response
float32 res
MyAction.action 과 같은 경우에는 다음처럼 작성합니다.
# Goal
float32 go
---
# Result
float32 res
---
# Feedback
string[] str
안에 넣을 수 있는 자료형은 이전시간에 다룬 적이 있습니다만 다시 확인하면 다음과 같습니다.
이렇게 인터페이스 관련 패키지를 만들게 되었습니다. 그러면 package.xml과 CMakeLists.txt 파일을 변경해주어야합니다.
package.xml에는 다음 코드가 추가되어야합니다.
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>builtin_interfaces</exec_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
여기서 rosdil_default_generators는 DDS에서 사용되는 IDL(Interface Definition_Language)의 생성과 관련된 내용입니다.
<buildtool_depend>rosidl_default_generators</buildtool_depend>
<exec_depend>builtin_interfaces</exec_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
이 3줄은 인터페이스 전용 패키지를 만들때 필수적인 의존성 패키지임으로 꼭 기억해주어야합니다.
그 다음은 CMakeLists.txt에 파일에 대한 추가입니다.
find_package(builtin_interfaces REQUIRED)
find_package(rosidl_default_generators REQUIRED)
# uncomment the following section in order to fill in
# further dependencies manually.
# find_package(<dependency> REQUIRED)
set(msg_files
"msg/MyMsg.msg"
)
set(srv_files
"srv/MySrv.srv"
)
set(action_files
"action/MyAction.action"
)
rosidl_generate_interfaces(${PROJECT_NAME}
${msg_files}
${srv_files}
${action_files}
DEPENDENCIES builtin_interfaces
)
다음을 추가하면됩니다.
그러면 이제 빌드를 진행해봅시다.
그러면 이제 인터페이스 패키지의 제작이 마무리됩니다. colcon build시에 3개의 폴더가 만들어진다는 것을 알고계실 것 입니다.
보통 ros는 install파일을 뒤져가면서 실행을 하게 됩니다.
그러면 우리가 방금 만든 인터페이스들은 install 어디에 저장이 되어 있는지 찾아보도록 합시다.
먼지 include 폴더에는 C++로 작업을 하는 경우에 해당합니다. /home/dubook/ros_study/install/ros_study_msgs/include/ros_study_msgs으로 이동하면 3가지 폴더가 보이실겁니다.
msg파일만 확인을 해봅시다.
다음처럼 파일들이 만들어진 것을 볼 수있습니다.
/home/dubook/ros_study/install/ros_study_msgs/local/lib/python3.10/dist-packages/ros_study_msgs
다음의 폴더를 들어가는 경우에는 Python에서 작동될 때 사용할 파일들이 들어있다고 합니다.
또한 DDS에서 사용되는 IDL파일의 경우에는 share 폴더를 확인해보면 됩니다.
/home/dubook/ros_study/install/ros_study_msgs/share/ros_study_msgs
이렇게 생긴 파일이며, 여러 패키지를 뜯어보면 idl파일로 지정된 경우를 많이 볼 수 있습니다만 글을 쓰는 저도 이 부분은 잘 모르기 때문에 나중에 한 번 정리를 해보도록 하겠습니다.
Python으로 방금 만든 인터페이스 패키지의 topic 을 사용하기
방금 만들었던 MyMsg.msg파일을 활용하는 방법을 적고자 합니다. service나 action은 아직 배우지 않았기 때문에 topic만 우선 살펴보도록 하겠습니다.
이전시간에 만들었던 my_publisher.py파일의 코드를 복사하도록 하겠습니다. 파일의 이름은 my_msg_test.py로 만들도록 하죠.
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
class my_msg_test(Node):
def __init__(self):
super().__init__('my_msg_test')
qos_profile = QoSProfile(depth=10)
self.publisher_ = self.create_publisher(, 'MyMsg', qos_profile)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0
def timer_callback(self):
msg =
msg
self.publisher_.publish(msg)
self.get_logger().info()
self.i += 1
def main(args=None):
rclpy.init(args=args)
my_msg_test_publisher = my_msg_test()
rclpy.spin(my_msg_test_publisher)
# Destroy the node explicitly
# (optional - otherwise it will be done automatically
# when the garbage collector destroys the node object)
my_msg_test_publisher.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
우선 my_publisher파일에서 뼈대만 살려보도록 합시다.
import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from ros_study_msgs.msg import MyMsg
다음처럼 MyMsg파일을 가져오게 합니다. 이후에 노드 부분을 수정합시다. 우리가 만들고자 하는 목표는 0.5초마다 우리 메시지 타입으로 발행하는 코드 작성입니다.
class my_msg_test(Node):
def __init__(self):
super().__init__('my_msg_test')
qos_profile = QoSProfile(depth=10)
self.publisher_ = self.create_publisher(MyMsg, 'MyMsg', qos_profile)
timer_period = 0.5 # seconds
self.timer = self.create_timer(timer_period, self.timer_callback)
self.i = 0.0
def timer_callback(self):
msg = MyMsg()
msg.num = self.i
self.publisher_.publish(msg)
self.get_logger().info(str(self.i))
self.i += 1
다음처럼 수정하고, entry_point 를 지정해주면서 진행을 하도록 합시다. setup.py 에 다음을 추가합시다. (나중에 나올 이야기 이지만 여러 노드를 실행하기 귀찮아지면 launch라는 개념도 등장하기도 합니다. 그건 나중 이야기입니다.)
"my_msg_test = ros_study_py.my_msg_test:main"
이후 package.xml 파일을 수정합니다.
<depend>ros_study_msgs</depend>
다음을 추가하고 저장을 하고 빌드하고 발행을 해봅시다. (ros_study환경을 불러오는거 잊지 마세요.)
ros2 run ros_study_py my_msg_test
다음으로 ros2 run하면
다음과 같이 나오는 것을 확인할 수 있습니다. 그러면 정상적으로 topic이 발행되고 있는 보려면 어떻게 해야할까요?
다른 터미널을 켠 후 ros2 topic list와 ros2 topic echo , ros2 interface show를 활용해서 알아보면됩니다. 아니라면 rqt_graph를 사용하는 방법도 있습니다.
다음처럼 topic 이 정상적으로 발행되고 있음을 확인할 수 있습니다. 다음은 C++에서 이를 진행해보도록 합시다.
C++으로 방금 만든 인터페이스 패키지의 topic 을 사용하기
아직 C++이 남아있었군요. 방금 한거 그대로 C++에서도 진행을 해보도록 합시다. C++에서도 my_msg_test.cpp 파일을 만들어주도록 합시다. 그리고 이에 대한 뼈대는 my_publisher.cpp에서 가져오도록 합시다.
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
using namespace std::chrono_literals;
class MyMsgTest : public rclcpp::Node
{
public:
MyMsgTest()
: Node(""), count_(0)
{
auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
publisher_ = this->create_publisher<>("", qos_profile);
timer_ = this->create_wall_timer(
500ms, std::bind(&MyMsgTest::timer_callback, this));
}
private:
void timer_callback()
{
auto message =
message
RCLCPP_INFO(this->get_logger(), "");
publisher_->publish(message);
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<>::SharedPtr publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
rclcpp::init(argc, argv);
rclcpp::spin(std::make_shared<MyMsgTest>());
rclcpp::shutdown();
return 0;
}
다음의 뼈대에서 작성을 진행하면 될 것입니다. 다음처럼 우리가 만들었던 메시지 인터페이스를 가져옵니다.
#include <chrono>
#include <functional>
#include <memory>
#include <string>
#include "rclcpp/rclcpp.hpp"
#include "ros_study_msgs/msg/my_msg.hpp"
그 다음에는 Node의 이름을 정하도록 합시다.
class MyMsgTest : public rclcpp::Node
{
public:
MyMsgTest()
: Node("mymsgtest_node"), count_(0)
{
auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
publisher_ = this->create_publisher<ros_study_msgs::msg::MyMsg>("MyMsg", qos_profile);
timer_ = this->create_wall_timer(
500ms, std::bind(&MyMsgTest::timer_callback, this));
}
private:
void timer_callback()
{
auto message = ros_study_msgs::msg::MyMsg();
message.num = count_;
RCLCPP_INFO(this->get_logger(), "Count: %zu", count_);
publisher_->publish(message);
count_++;
}
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<ros_study_msgs::msg::MyMsg>::SharedPtr publisher_;
size_t count_;
};
다음처럼 수정을 해줍시다. 그리고 packagae.xml을 수정합니다.
<depend>ros_study_msgs</depend>
그리고 CMakeLists.txt파일을 수정해주도록 합시다.
find_package(ros_study_msgs REQUIRED)
add_executable(my_msg_test src/my_msg_test.cpp)
ament_target_dependencies(my_msg_test rclcpp ros_study_msgs)
install(TARGETS
my_msg_test
DESTINATION lib/${PROJECT_NAME})
다음을 추가해주도록 합니다.
그러면 빌드하고 ros2 run시키고 다른 터미널에서 ros2 topic list에서 뜨는지만 확인하면 됩니다.
(과정 생략)
ros2 run ros_study_cpp my_msg_test
이것으로 topic받기를 마무리 합니다.
다음시간에는 ROS2에서 Python을 활용해서 패키지를 설계하는 전과정을 진행해보도록 하겠습니다. 아마 글이 더 길어질 것으로 예상됩니다.
'공부#Robotics#자율주행 > (ROS2) 기초' 카테고리의 다른 글
(ROS2 기초)9. Python과 C++ 패키지 설계부터 토픽, 서비스, 액션, 파라미터로 코딩하기 (문제 파트) (0) | 2023.05.12 |
---|---|
(ROS2 기초)7. ROS2에서 C++으로 기초 코딩하기 (1) | 2023.05.11 |
(ROS2 기초)6. 코드 스타일, ROS2에서 파이썬으로 기초 코딩하기 (2) | 2023.05.09 |
(ROS2 기초)5. ROS에서 코딩하기 전 알아야할 내용 (0) | 2023.05.08 |
(ROS2 기초)4. ROS에서 사용하는 도구 (1) | 2023.05.08 |