본 포스트는 ROS1에 대한 전반적인 지식이 있다는 가정 하에 작성되었으며, The Construct Youtube 영상을 참고하였습니다.
1 패키지란?
ROS2도 프로그램을 다루기 위해 ROS1처럼 패키지를 사용합니다. 패키지는 특정한 ROS2 프로그램을 포함하는 모든 파일들(cpp, python, configuration, compliation, launch, parameters 파일들)이라고 생각하면 되겠습니다.
ROS2에서는 2가지 종류의 패키지를 만들 수 있는데... 하나는 파이썬 패키지, 다른 하나는 CMake(C++) 패키지 입니다.
모든 파이썬 패키지는 다음과 같은 파일 및 디렉터리 구조를 갖습니다.
- package.xml - 패키지에 대한 meta-information(패키지 관리자, 의존성 등등...)에 대한 파일
- setup.py - 패키지를 컴파일하는 방법에 대한 설명 파일
- setup.cfg - 해당 스크립트가 설치될 위치를 정의하는 파일
- /<package_name> - ROS2에서는 패키지를 만들면 해당 패키지와 똑같은 이름의 디렉토리가 패키지 내부에 생성됩니다. 우리는 파이썬 스크립트를 이 폴더 안에다가 넣을 것이고, 처음 생성하면 기본적으로 __init__.py 파일이 생성됩니다.
일부 패키지들은 예를 들어 launch 같은 추가 디렉토리를 갖고 있기도 합니다.
2 패키지 만들기
이제 패키지를 직접 만들어보겠습니다. 패키지를 만들 때는 ROS1과 비슷하게, ROS2 workspace 에서 작업을 할 것입니다. ROS1에서는 일반적으로 catkin_ws 라는 workspace를 만들었지만, ros2에서는 일반적으로 ros2_ws를 사용합니다.
예제 2.1.
(이전 포스트의 내용을 진행했다는 가정 하에 설명합니다)
먼저, 터미널 창에서 ROS2를 source 합니다.
$ source /opt/ros/humble/setup.bash
ros2_ws 디렉토리에서 패키지를 생성합니다.
$ cd ~/ros2_ws/
$ cd src
$ ros2 pkg create --build-type ament_python my_package --dependencies rclpy
실행 결과
즉, ros2에서 python을 사용하는 사용자 정의 패키지를 만드는 명령어는 다음과 같습니다(물론 src 디렉토리에서 실행해야 합니다)
$ ros2 pkg create --build-type ament_python <package_name> --dependencies <package_dependencies>
다음으로 패키지를 빌드하고, source하면 패키지를 사용할 수 있습니다(ros1의 catkin_make에 대응됩니다)
$ cd ~/ros2_ws && colcon build --symlink-install
$ source install/setup.bash
만약 이전 포스트에서 설명한 대로 bashrc 파일을 수정하셨으면, 아래와 같이 명령어를 입력합니다
$ cb
$ sb
패키지가 잘 만들어졌는지 확인해봅니다
$ ros2 pkg list | grep my_package
만약 특정한 패키지만 빌드하고 싶다면, 다음 명령을 실행합니다
colcon build --packages-select <package_name> --symlink-install
3. ROS2 프로그래밍 해보기
1. my_package 패키지 내부의 my_directory에 simple.py 를 하나 만들고, (편집기는 vs code를 사용했습니다)
$ cd ~/ros2_ws/src/my_package/my_package
$ code simple.py
다음과 같이 코드를 작성합니다.
simple.py
import rclpy # ROS1의 rospy에 대응
from rclpy.node import Node
def main(args=None):
rclpy.init(args=args)
print("ROS2 is working! ")
rclpy.shutdown()
if __name__ == '__main__':
main()
2. my_package 패키지 내부에 launch 디렉토리를 만듭니다.
$ cd ~/ros2_ws/src/my_package
$ mkdir launch
3. launch 파일을 하나 만들고,
$ cd ~/ros2_ws/src/my_package/launch
$ code my_package_launch_file.launch.py
4. 다음과 같이 작성하고,
my_pacakge_launch_file.launch.py
from launch import LaunchDescription
from launch_ros.actions import Node
def generate_launch_description():
return LaunchDescription([
Node(
package='my_package',
executable='simple_node',
output='screen'),
])
실행 권한을 부여해줍니다
$ chmod +x my_package_launch_file.launch.py
5. my_package 패키지 디렉토리에 있는 setup.py 파일을 다음과 같이 수정합니다.
위 파일을 아래와 같이 수정합니다
setup.py
from setuptools import setup
import os
from glob import glob
package_name = 'my_package'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name), glob('launch/*.launch.py')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='justin',
maintainer_email='justin@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'simple_node = my_package.simple:main'
],
},
)
6. 빌드합니다.
$ cb
$ sb
7. 실행해봅니다.
$ ros2 launch my_package my_package_launch_file.launch.py
4. setup.py 파일 설명
setup.py 파일은 패키지를 적절하게 컴파일하기 위한 내용을 담고있습니다.
이전에 사용했던 코드는 다음과 같았습니다.
from setuptools import setup
import os
from glob import glob
package_name = 'my_package'
setup(
name=package_name,
version='0.0.0',
packages=[package_name],
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name), glob('launch/*.launch.py'))
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='somebody very awesome',
maintainer_email='user@user.com',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'simple_node = my_package.simple:main'
],
},
)
이 setup.py의 주요 목표는 위 코드로부터 실행 파일을 만드는 것입니다. 이를 위해서는 entry_points 라는 이름의 딕셔너리 타입 변수에다 console_scripts에다 다음과 같은 형식으로 입력해줍니다.
'<executable_name> = <package_name>.<script_name>:main'
우리가 이전에 사용했던 코드와 비교해보면...
import os
from glob import glob
from setuptools import setup
package_name = 'my_package'
setup(
#code
...
#code
entry_points={
'console_scripts': [
'simple_node = my_package.simple:main'
],
},
#code
...
)
즉, 위 코드에서는 my_package라는 패키지의 simple.py라는 파일로 simple_node라는 이름의 실행 노드를 만들어줍니다.
또한, 컴파일 과정 중에 setup.py의 파라미터 중 data_files라는 파라미터를 사용하는 launch 파일들의 파이썬 setup tools에 다음과 같이 정보를 건네줄 수 있습니다.
import os
from glob import glob
from setuptools import setup
package_name = 'my_package'
setup(
#code
...
#code
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name), glob('launch/*.launch.py'))
],
#code
...
#code
)
위 코드의 목적은 launch files를 설치하기 위함입니다. 위의 예시에서는 my_package라는 패키지 내부의 launch 디렉토리에 있는 모든 launch 파일들(launch/*.launch.py)을 ~/ros2_ws/install/my_package/share/my_package/ 디렉토리에다 설치한다는 것입니다.
5. ROS2 Nodes
ROS2에서 각의 노드는 하나의 모듈만 다뤄야 합니다(예를 들어, 모터만 제어하는 노드, 라이다만 제어하는 노드 등...). 각 노드는 다른 방식을 사용하여 다른 노드와 통신할 수 있습니다.
예제 2.2
아까 위에서 사용했던 my_package의 simple.py를 수정합니다.
import rclpy
from rclpy.node import Node
class MyNode(Node):
def __init__(self):
# super()를 호출하여 Node object를 초기화
# 매개변수로 노드의 이름 전달
super().__init__('Test_node')
# 2개의 파라미터를 받는 타이머 생성
# - 콜백 사이의 시간 (0.2초)
# - 콜백때마다 호출할 함수 (timer_callback)
self.create_timer(0.2, self.timer_callback)
def timer_callback(self):
# 터미널에 메시지 띄우기
self.get_logger().info("This message is from Test_node")
def main(args=None):
# ROS2 communication 초기화
rclpy.init(args=args)
# MyNode object 생성
node = MyNode()
# ctrl+c 누를 때까지 노드 실행
rclpy.spin(node)
# ROS2 communication 종료
rclpy.shutdown()
if __name__ == '__main__':
main()
빌드 및 source 후, 해당 노드를 실행해봅니다.
$ cb
$ sb
$ ros2 launch my_package my_package_launch_file.launch.py
다음 명령으로 node 목록을 확인할 수 있습니다.
$ ros2 node list
또한, 다음 명령으로 node의 정보를 확인할 수 있습니다.
$ ros2 node info /Byakugan
6. Client Libraries
지금까지 위의 예제들에서 Client Libraries를 사용했었습니다. ROS1의 rospy, roscpp에 대응되는 것이라고 보시면 됩니다. ROS2에서는 ROS Client Library(RCL)이라고 하며, 다양한 ROS API가 필요로 하는 기본적인 기능들이 구현되어있습니다.
- rclcpp = C++ client library
- rclpy = Python client library
'Study > [ROS2] ROS2 Basic' 카테고리의 다른 글
ROS2 with C++ - Topic (0) | 2023.07.28 |
---|---|
ROS2 with python - Actions (0) | 2023.07.28 |
ROS2 with python - Executors and Callback Groups (0) | 2023.07.28 |
ROS2 with python - Understanding ROS2 Services (0) | 2023.07.27 |
ROS2 with python - Understanding ROS2 Topics (0) | 2023.07.26 |