[Project#4] RL Environment 런치 파일 코드 분석
[Project#] 시리즈는 프로젝트(Trash Throwing Simulation with Deep Reinforcement Learning) 제작 과정을 설명한다. 본 포스팅은 RL Environment 코드 분석에 대한 내용이다.
모든 내용은 이전 글과 이어진다. 이전 글을 오류 없이 모두 진행했다고 가정하고 글을 작성하였다. (이전 글 LINK)
1. my_environment.launch.py
- launch 파일은 여러 노드를 한 번에 실행시키기 위한 파일이다. ROS 1에서는 XML 기반이었지만 ROS 2에서는 Python 방식을 추가하였다.
- my_environment.launch.py 파일은 my_environment_pkg 패키지 안에 있는 런치 파일이며, 강화 학습을 적용하기 위한 환경을 생성한다.
- 전체 코드의 일부분을 가져와서 설명하는 방식으로 작성하였다.
1.1. 전체 코드
import os
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import ExecuteProcess
def generate_launch_description():
my_sphere_files = get_package_share_directory('my_sphere_pkg')
my_doosan_robot_files = get_package_share_directory('my_doosan_pkg')
my_environmets_files = get_package_share_directory('my_environment_pkg')
# Start doosan robot and controller
doosan_robot = IncludeLaunchDescription(PythonLaunchDescriptionSource(my_doosan_robot_files + '/launch/my_doosan_controller.launch.py'))
# Start sphere mark
sphere_mark = IncludeLaunchDescription(PythonLaunchDescriptionSource(my_sphere_files + '/launch/my_sphere.launch.py'))
# Start Rviz
rviz_file = my_environmets_files + "/rviz/my_rviz_env.rviz"
rviz_node = Node( package='rviz2',
executable='rviz2',
name='rviz2',
output='log',
arguments=['-d', rviz_file])
# Start Gazebo
world_file_name = 'my_world.world'
world = os.path.join(get_package_share_directory('my_environment_pkg'), 'worlds', world_file_name)
gazebo_node = ExecuteProcess(cmd=['gazebo', '--verbose', world,'-s', 'libgazebo_ros_factory.so'], output='screen')
# Node
ld = LaunchDescription()
ld.add_action (doosan_robot)
ld.add_action (sphere_mark)
ld.add_action (gazebo_node)
ld.add_action (rviz_node)
return ld
1.2. 모듈 가져오기
import os
from launch import LaunchDescription
from launch_ros.actions import Node
from ament_index_python.packages import get_package_share_directory
from launch.actions import IncludeLaunchDescription
from launch.launch_description_sources import PythonLaunchDescriptionSource
from launch.actions import ExecuteProcess
os
: Operation System의 약자로, 운영체제에서 제공하는 여러 기능을 파이썬에서 수행할 수 있게 해준다. 런치 파일에선 주로 파일 경로를 확인하기 위해 사용한다.LaunchDescription
: 어떤 노드를 실행시킬지 기술해둔 Description을 작성한다. 다만 특정한 규칙으로 실행시킨 Node에 대한 옵션을 지정해주어야 하며, 여러 Node들의 실행이 필요할 수 있기에, 기본적으로 List의 구조를 가진다.Node
: 노드 클래스를 의미한다.get_package_share_directory
: ROS2에서 지정된 패키지의share
디렉토리에 대한 경로를 얻기 위해 사용되는 함수이다. 이 디렉토리에는 일반적으로 구성 파일, 시작 파일 및 데이터 파일과 같은 여러 패키지 간에 공유되는 실행 불가능한 파일이 포함되어 있다.IncludeLaunchDescription
: 다른 launch 파일 안에 launch 파일을 포함하는 데 사용된다.PythonLaunchDescriptionSource
: Python launch 파일의 위치를 지정하는 데 사용된다.ExecuteProcess
**: 프로세스 하나를 실행시킨다는 구분의 개념으로 생각하면 좋다.
1.3. generate_launch_description 함수
- ros2 launch 커멘드를 통해 launch file을 실행시키면, 이 이름을 가진 함수를 찾아들어갑니다. 그래서 모든 launch file에는 빠지지 않고 제일 먼저 등장하는 함수이다.
-
share 폴더 경로 얻기
my_sphere_files = get_package_share_directory('my_sphere_pkg') my_doosan_robot_files = get_package_share_directory('my_doosan_pkg') my_environmets_files = get_package_share_directory('my_environment_pkg')
- 빌드 시 install 폴더 안에 각 패키지에 대한 폴더가 생기며, 그 안에 share 폴더가 있다. get_package_share_directory 함수는 인자로 받은 패키지의 폴더 경로를 가져온다.
- 각 변수의 정확한 값(경로)은 아래와 같다.
> my_sphere_files: /home/dndqodqks/ros2_ws/install/my_sphere_pkg/share/my_sphere_pkg > my_doosan_robot_files: /home/dndqodqks/ros2_ws/install/my_doosan_pkg/share/my_doosan_pkg > my_environmets_files: /home/dndqodqks/ros2_ws/install/my_environment_pkg/share/my_environment_pkg
-
런치 파일 가져오기
# Start doosan robot and controller doosan_robot = IncludeLaunchDescription(PythonLaunchDescriptionSource(my_doosan_robot_files + '/launch/my_doosan_controller.launch.py')) # Start sphere mark sphere_mark = IncludeLaunchDescription(PythonLaunchDescriptionSource(my_sphere_files + '/launch/my_sphere.launch.py'))
- PythonLaunchDescriptionSource와 IncludeLaunchDescription 함수를 활용하여 런치 파일에 대한 객체를 선언할 수 있다.
-
Rviz 노드 생성하기
# Start Rviz rviz_file = my_environmets_files + "/rviz/my_rviz_env.rviz" rviz_node = Node( package='rviz2', executable='rviz2', name='rviz2', output='log', arguments=['-d', rviz_file])
- rviz 파일의 주소를 저장한 후 rviz 노드를 생성한다.
- Node 객체의 각 파라미터의 의미는 아래와 같다.
package
: 실행시킬 Node가 포함된 package를 선택한다.executable
: 노드가 시작될 때 실행될 실행 파일의 이름을 지정한다.name
: 이 매개변수는 시작할 노드의 이름을 지정한다. ROS2 시스템의 각 노드는 고유한 이름을 가져야 한다. 이 경우 노드의 이름도rviz2
이다.output
: 이 매개변수는 노드가 시작될 때 콘솔에 표시될 출력 유형을 지정한다. 이 매개변수의 옵션은log
(로그 메시지만 표시),screen
(모든 출력을 콘솔에 표시) 및both
(로그 메시지와 출력을 모두 콘솔에 표시)이다. 이 경우 출력 유형은log
입니다.arguments
: 런치 파일이 노드로 시작될 때 런치 파일에 전달되어야 하는 추가 인수를 지정한다. 이 경우-d
인수는rviz2
구성 파일의 경로와 함께rviz2
런치 파일에 전달되어rviz2
에 3D 시각화를 표시하는 방법을 알려준다.
-
가제보 노드 생성하기
# Start Gazebo world_file_name = 'my_world.world' world = os.path.join(get_package_share_directory('my_environment_pkg'), 'worlds', world_file_name) gazebo_node = ExecuteProcess(cmd=['gazebo', '--verbose', world,'-s', 'libgazebo_ros_factory.so'], output='screen')
os.path.join
함수를 활용하여 .world 파일의 주소를 생성한다.-
.world 파일의 주소는 아래와 같다.
/home/dndqodqks/ros2_ws/install/my_environment_pkg/share/my_environment_pkg/worlds/my_world.world
- ExecuteProces를 사용하여 gazebo_node를 생성한다. ExecuteProces는 터미널창에 입력하는 명령어라고 생각하면 편하다. 각각의 내용은 다음과 같다.
--verbose
: 상세 출력을 활성화한다.world
: .world 파일의 경로를 의미하며, 해당 파일을 바탕으로 시뮬레이터의 배경을 생성한다.-s
: 로드 되어야 하는 플러그인을 지정한다. 위 코드에서는 libgazebo_ros_factory.so가 필요한 플러그인인다.output
: gazebo 명령의 출력이 터미널에 표시되도록 한다.
-
LaunchDescription 객체 반환
# Node ld = LaunchDescription() ld.add_action (doosan_robot) ld.add_action (sphere_mark) ld.add_action (gazebo_node) ld.add_action (rviz_node) return ld
- LaunchDescription 객체를 선언하고 add_action 함수를 활용하여 실행하고자 하는 노드를 추가한다. 이후 객체를 리턴하여 추가된 노드를 실행한다.
-
LaunchDescription 객체에 실행하고자 하는 노드를 추가하는 다른 방법도 있다. LaunchDescription을 선언할 때 인자로 노드를 넣어주는 것이다. 이때 리스트의 형태로 입력해야 한다. 예시는 아래와 같다. 어떤 방식이 ROS2에 더 어울리는지 모르겠다.
return LaunchDescription([doosan_robot, sphere_mark, gazebo_node, rviz_node])
2. my_doosan_controller.launch.py
- my_doosan_controller.launch.py은 my_doosan_pkg 안에 있는 런치 파일이다.
2.1. 전체 코드
import os
from launch_ros.actions import Node
from launch import LaunchDescription
from launch.substitutions import Command
from launch.actions import ExecuteProcess
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
#robot model to option m1013 or a0912
robot_model = 'a0912'
#robot_model = 'm1013'
xacro_file = get_package_share_directory('my_doosan_pkg') + '/description'+'/xacro/'+ robot_model +'.urdf.xacro'
# Robot State Publisher
robot_state_publisher = Node(package ='robot_state_publisher',
executable ='robot_state_publisher',
name ='robot_state_publisher',
output ='both',
parameters =[{'robot_description': Command(['xacro', ' ', xacro_file])
}])
# Spawn the robot in Gazebo
spawn_entity_robot = Node(package ='gazebo_ros',
executable ='spawn_entity.py',
arguments = ['-entity', 'my_doosan_robot', '-topic', 'robot_description'],
output ='screen')
# load and START the controllers in launch file
load_joint_state_broadcaster = ExecuteProcess(
cmd=['ros2', 'control', 'load_controller', '--set-state', 'start','joint_state_broadcaster'],
output='screen')
load_joint_trajectory_controller = ExecuteProcess(
cmd=['ros2', 'control', 'load_controller', '--set-state', 'start', 'joint_trajectory_controller'],
output='screen')
return LaunchDescription([robot_state_publisher, spawn_entity_robot, load_joint_state_broadcaster, load_joint_trajectory_controller ])
2.2. 모듈 가져오기
import os
from launch_ros.actions import Node
from launch import LaunchDescription
from launch.substitutions import Command
from launch.actions import ExecuteProcess
from ament_index_python.packages import get_package_share_directory
- 위에서 설명한 모듈은 설명하지 않는다.
substitutions
모듈은launch
패키지에 의해 시작된 노드에 대한 명령줄 인수를 동적으로 생성하는 데 사용할 수 있는 일련의 클래스 및 함수를 제공한다.Command
클래스는 명령을 실행하고 그 출력을 대체 값으로 사용할 수 있는 대체 유형이다.- 정확히 무엇을 의미하는지 이해하지 못하였다.
2.3. generate_launch_description 함수
-
xacro_file 경로 저장하기
robot_model = 'a0912' #robot_model = 'm1013' xacro_file = get_package_share_directory('my_doosan_pkg') + '/description'+'/xacro/'+ robot_model +'.urdf.xacro'
- description/xacro 폴더 내부에 있는 a0912 모델의 xacro 파일의 경로를 저장한다.
- xacro 파일에는 로봇의 형태 및 마찰력 등이 기술되어 있다.
-
robot state publisher 노드 생성하기
# Robot State Publisher robot_state_publisher = Node(package ='robot_state_publisher', executable ='robot_state_publisher', name ='robot_state_publisher', output ='both', parameters =[{'robot_description': Command(['xacro', ' ', xacro_file]) }])
- robot_state_publisher 패키지의 robot_state_publisher 노드를 실행하며, screen과 log 둘 다 출력한다.
- 파라미터로 딕셔너리 형태의 데이터를 입력하고 있다 . 여기서
Command
함수는 어떤 역할을 하는지 모르겠다.
-
spawn entity robot 노드 생성하기
# Spawn the robot in Gazebo spawn_entity_robot = Node(package ='gazebo_ros', executable ='spawn_entity.py', arguments = ['-entity', 'my_doosan_robot', '-topic', 'robot_description'], output ='screen')
- “spawn entity robot”은 로봇 모델을 시뮬레이션 환경에 생성하는 것을 의미한다. “spawn”은 “생성하다”라는 의미이고, “entity”는 “개체” 또는 “모델”을 의미한다. 따라서 “spawn entity robot”은 “로봇 모델을 시뮬레이션 환경에 생성하다”라는 뜻이다.
- gazebo_ros 패키지의 spawn_entity.py 파일을 실행하며, 터미널에 정보를 출력한다. 이때 2개의 인자를 arguments로 보낸다. 첫 번째는 ‘-entity my_doosan_robot’이다. 이 인수는 시뮬레이션에서 스폰될 로봇 모델의 이름을 지정한다. 이 경우 이름은 my_doosan_robot으로 설정된다. 두 번째는 ‘-topic robot_description’이다. 이 인수는 로봇 설명이 게시될 ROS topic을 지정한다. 이 경우 topic 이름은 robot_description으로 설정됩니다.
❗arguments VS parameters
arguments
는 실행될 때 노드의 실행 파일에 전달되는 명령줄 인수이다. 이는 일반적으로 노드에 대한 런타임 구성을 제공하거나 노드가 올바르게 작동하는 데 필요한 추가 정보를 지정하는 데 사용된다.parameters
는 노드에 대해 설정되고 런타임 시 노드에서 액세스할 수 있는 구성 값이다.parameters
는 일반적으로 시작 파일에 설정되며 노드를 다시 컴파일하지 않고도 변경할 수 있다. 센서의 IP 주소 또는 로봇의 최대 속도와 같이 노드의 수명 동안 일정하게 유지되는 구성을 제공하는 데 사용할 수 있다.
요약하면
arguments
와parameters
모두 노드에 대한 구성 값을 제공하지만arguments
는 노드가 시작될 때 명령줄에 전달되며 노드가 시작될 때마다 변경할 수 있으며,parameters
는 시작 파일에 있으며 노드를 다시 시작하지 않고도 변경할 수 있다. -
load joint state broadcaster 프로세스 생성하기
# load and START the controllers in launch file load_joint_state_broadcaster = ExecuteProcess( cmd=['ros2', 'control', 'load_controller', '--set-state', 'start','joint_state_broadcaster'], output='screen')
ros2 control
도구를 사용하여 컨트롤러를 로드하고 시작하는 명령을 실행하는load_joint_state_broadcaster
라는ExecuteProcess
객체를 생성한다.- 실행되는 명령은
ros2 control load_controller --set-state start joint_state_broadcaster
이다. 이 명령은joint_state_broadcaster
라는 컨트롤러를 로드하고 해당 상태를start
로 설정한다. output
매개변수는'screen'
으로 설정되며, 이는 프로세스에서 생성된 모든 출력이 콘솔에 표시됨을 의미한다.
-
load joint trajectory controller 프로세스 생성하기
load_joint_trajectory_controller = ExecuteProcess( cmd=['ros2', 'control', 'load_controller', '--set-state', 'start', 'joint_trajectory_controller'], output='screen')
ros2 control
도구를 사용하여 컨트롤러를 로드하고 시작하는 명령을 실행하는 데 사용되는load_joint_trajectory_controller
라는ExecuteProcess
객체를 생성한다.- 실행 중인 명령은
ros2 control load_controller --set-state start joint_trajectory_controller
이다. 이 명령은joint_trajectory_controller
라는 컨트롤러를 로드하고 상태를start
로 설정한다. output
매개변수는'screen'
으로 설정되며, 이는 프로세스에서 생성된 모든 출력이 콘솔에 표시됨을 의미한다.
-
LaunchDescription 객체 반환
return LaunchDescription([robot_state_publisher, spawn_entity_robot, load_joint_state_broadcaster, load_joint_trajectory_controller ])
- 위에서 생성한 노드와 프로세스를 LaunchDescription 객체로 반환한다.
3. my_sphere.launch.py
- my_sphere_pkg 패키지 안에 있는 my_sphere.launch.py 파일이다.
3.1. 전체 코드
import os
from launch_ros.actions import Node
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch.substitutions import LaunchConfiguration
from ament_index_python.packages import get_package_share_directory
def generate_launch_description():
pkg_dir = get_package_share_directory('my_sphere_pkg')
# SDF
sdf_file_name = 'sdf/sphere_goal/model.sdf'
sdf = os.path.join(pkg_dir, 'models', sdf_file_name)
spawn_entity = Node(package='gazebo_ros',
executable='spawn_entity.py',
arguments=['-entity', 'my_sphere', '-file', sdf, '-x','0.5', '-y','0.5', '-z','1'],
output='screen')
# Nodes
# node_mark --> coordinate_node.py --> reads the position of the sphere in Gazebo and publishes the Marker Topic
node_mark = Node(package ='my_sphere_pkg', executable ='reader_mark_node', output ='screen')
return LaunchDescription([spawn_entity, node_mark])
3.2. Import
import os
from launch_ros.actions import Node
from launch import LaunchDescription
from launch.actions import ExecuteProcess
from launch.substitutions import LaunchConfiguration
from ament_index_python.packages import get_package_share_directory
LaunchConfiguration
: 런치 파일의 다른 부분에서 대체 항목으로 사용할 수 있는 시작 시간 구성 값을 지정하는 방법을 제공한다. 예를 들어,LaunchConfiguration
을 사용하여 네트워크 엔드포인트의 IP 주소 또는 포트 번호, 구성 파일 경로 또는 기타 시작 시간 매개변수를 지정할 수 있다.
3.3. generate_launch_description 함수
-
share directory 경로 저장
pkg_dir = get_package_share_directory('my_sphere_pkg')
my_sphere_pkg
패키지의 share 폴더 경로를 저장한다.
-
SDF 파일 경로 저장
# SDF sdf_file_name = 'sdf/sphere_goal/model.sdf' sdf = os.path.join(pkg_dir, 'models', sdf_file_name)
- sdf 파일의 경로를 저장하고,
os.path.join
함수를 활용하여 절대 경로를 저장한다.
- sdf 파일의 경로를 저장하고,
-
Gazebo에 구체 생성
spawn_entity = Node(package='gazebo_ros', executable='spawn_entity.py', arguments=['-entity', 'my_sphere', '-file', sdf, '-x','0.5', '-y','0.5', '-z','1'], output='screen')
- gazebo_ros 패키지의 spawn_entity.py 파일을 실행한다.
- Gazebo 시뮬레이션 환경의 위치(0.5, 0.5, 1)에서 이름이
my_sphere
인 entity(객체)를 생성합니다. entity에 대한 모델 정의는sdf
변수로 지정된다.
-
node_mark 노드 생성
node_mark = Node(package ='my_sphere_pkg', executable ='reader_mark_node', output ='screen')
- my_sphere_pkg 패키지의 reader_mark_node 노드를 실행한다.
-
LaunchDescription 객체 반환
return LaunchDescription([spawn_entity, node_mark])
- 각 노드를 LaunchDescription 객체로 반환한다.
4. 결과
5. 참고 문헌
- https://github.com/dvalenciar/robotic_arm_environment
- https://wikidocs.net/3141
- ROS 2 Launch, launch file 작성