이번 실습을 위해 모바일 로봇에서 가장 많이 사용되는 타입의 로봇, FusionBot을 준비하였습니다. FusionBot을 통해 ROS에서의 로봇 표현 방법을 익힌 뒤, 실제 CAD 파일에서 Gazebo 상의 로봇을 구현하는 실습을 진행해봅시다.
cd ~/ros2_ws/src
git clone https://github.com/RB2023ROS/du2023-gz.git
cd du2023-gz
./setup_scripts.sh
일반적으로, 로봇은 Links와 Joints 두가지 요소로 이루어집니다.
다양한 종류의 joint들이 존재하지만, 이론적으로 이들은 결국 prismatic + revolute joint의 결합으로 설명될 수 있습니다.
그리고 ROS에서는 개발 상 편의를 위해 크게 6가지의 joint를 사용하고 있지요.
Link와 Joint로 결합된 로봇을 결국 텍스트로 표현할 수 있지 않겠냐는 기본 전제 하에, 로봇 공학자들은 URDF - Unified Robot Description Format라는 표준을 만들게 됩니다. 실제로 urdf만 있다면 시뮬레이터 종류에 상관 없이 동일한 로봇 외형을 등장시킬 수 있게 됩니다.
URDF는 XML 문법을 사용하고 있으며 ROS 1의 launch file과 같이 다양한 tag를 통해 로봇을 표현하게 됩니다. 예시를 통해 URDF에 대한 이해도를 가져봅시다.
URDF의 link가 가질 수 있는 속성들은 다음과 같습니다.
<link name="base_link">
<inertial>
<origin xyz="0 0 0" rpy="0 0 0" />
<mass value="8.3" />
<inertia ixx="5.249466E+13" ixy="-1.398065E+12" ixz="-3.158592E+12" iyy="5.786727E+13" iyz="-5.159120E+11" izz="3.114993E+13" />
</inertial>
<visual>
<origin xyz="0 0 0" rpy="0 3.1415 3.1415" />
<geometry>
<mesh filename="package://neuronbot2_description/meshes/neuronbot2/base_link.stl" scale="0.001 0.001 0.001" />
</geometry>
<material name="black" />
</visual>
<collision>
<origin xyz="0 0 0.125" rpy="0 0 0" />
<geometry>
<box size="0.25 0.25 0.25" />
</geometry>
<material name="black" />
</collision>
</link>
URDF의 Joint가 가질 수 있는 속성들은 다음과 같습니다.
<joint name="r_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="wheel_right_link"/>
<origin xyz="0.0 -0.09 0.0415" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
<joint name="l_wheel_joint" type="continuous">
<parent link="base_link"/>
<child link="wheel_left_link"/>
<origin xyz="0.0 0.109 0.0415" rpy="0 0 0"/>
<axis xyz="0 1 0"/>
</joint>
urdf의 joint는 절대 좌표를 기준으로 하는 extrinsic 체계를 갖습니다.
기타 속성들
URDF 스크립트를 사람이 모두 작성하기는 매우 비효율적입니다. 더불어, Gazebo에서만 사용하는 속성을 따로 분리하고 싶은 경우, 파일을 나누어 관리하고 싶을 것입니다. 이러한 욕구를 충족시키기 위해서 ROS는 URDF의 작성을 보다 편하게 해주는 XML Macro, Xacro를 지원하고 있습니다.
특히 xacro는 수식, 조건을 사용 가능하기 때문에 로봇 파일을 다루기 매우 용이하며, 특정 요소를 모듈화 후 재사용하는 등 효율적인 URDF 작성이 가능하도록 도와줍니다.
<xacro:macro name="pr2_arm" params="suffix parent reflect">
<pr2_upperarm suffix="${suffix}" reflect="${reflect}" parent="${parent}" />
<pr2_forearm suffix="${suffix}" reflect="${reflect}" parent="elbow_flex_${suffix}" />
</xacro:macro>
<xacro:pr2_arm suffix="left" reflect="1" parent="torso" />
<xacro:pr2_arm suffix="right" reflect="-1" parent="torso" />
...
<pr2_upperarm suffix="left" reflect="1" parent="torso" />
<pr2_forearm suffix="left" reflect="1" parent="elbow_flex_left" />
<pr2_upperarm suffix="right" reflect="-1" parent="torso" />
<pr2_forearm suffix="right" reflect="-1" parent="elbow_flex_right" />
CMake를 통해 손쉽게 Xacro 파일들을 URDF로 자동 변환하는 작업을 실습해봅시다.
colcon build --packages-select fusionbot_description
source install/setup.bash
패키지 빌드가 발생하면 모든 관련 파일들의 symbolic link가 workspace의 install//share 폴더에 생성됩니다. CMake는 이 share 폴더 내에 있는 symbolic link를 위주로 작업하므로 새로운 파일이 생겼다면 주기적으로 colcon build를 실행하는 것을 추천합니다.
find_package(xacro REQUIRED)
# Xacro files
file(GLOB xacro_files urdf/*.urdf.xacro)
foreach(it ${xacro_files})
# remove .xacro extension
string(REGEX MATCH "(.*)[.]xacro$" unused ${it})
set(output_filename ${CMAKE_MATCH_1})
# create a rule to generate ${output_filename} from {it}
xacro_add_xacro_file(${it} ${output_filename})
list(APPEND urdf_files ${output_filename})
endforeach(it)
add_custom_target(media_files ALL DEPENDS ${urdf_files})
sudo apt install ros-foxy-xacro -y
colcon build --packages-select fusionbot_description
<?xml version="1.0" ?>
<!-- =================================================================================== -->
<!-- | This document was autogenerated by xacro from /home/swimming/nav2_ws/src/nav2_rosdevday_2021/djhrd_ros2/fusionbot_description/urdf/fusionbot.urdf.xacro | -->
<!-- | EDITING THIS FILE BY HAND IS NOT RECOMMENDED | -->
<!-- =================================================================================== -->
<robot name="fusionbot">
<material name="silver">
<color rgba="0.700 0.700 0.700 1.000"/>
</material>
...
기존 fusionbot.urdf를 제거한 뒤 자동 생성되는 모습을 확인해보세요.
CMake는 사용자의 편의를 위해 추가해둔 것일 뿐 키워드를 통해 Xacro ⇒ URDF로의 변환도 가능합니다. 편한 방법을 사용하시기 바랍니다. (workspace를 sourcing 하셔야 실행 가능합니다.)
xacro fusionbot.urdf.xacro > fusionbot.urdf
urdf를 모두 작성했다면, 이제 ROS에게 이 정보를 전달해야 합니다. 이를 담당하는 패키지인 joint state publisher와 robot state publisher에 대해 학습해 봅시다.
ros2 launch fusionbot_description description.launch.py
WSL2 + Windows 시스템에서는 로봇 외관이 보이지 않을 수 있습니다.
joint state publisher는 로봇 내 존재하는 다양한 joint 값들을 실시간으로 갱신하여 /joint_states라는 topic으로 publish 합니다. 해당 topic은 sensor_msgs/msg/JointState msg를 사용하며, 각 joint들의 이름, 현재 위치, 속도와 힘을 배열 형태로 담게 됩니다.
$ ros2 interface show sensor_msgs/msg/JointState
# This is a message that holds data to describe the state of a set of torque controlled joints.
#
#
# The state of each joint (revolute or prismatic) is defined by:
# * the position of the joint (rad or m),
# * the velocity of the joint (rad/s or m/s) and
# * the effort that is applied in the joint (Nm or N).
#
...
std_msgs/Header header
string[] name
float64[] position
float64[] velocity
float64[] effort
예제 실행 시 등장했던 조그만 창은 joint_state_publisher_gui라고 불리며, 로봇 내 조작이 가능한 joint들을 간단하게 제어할 수 있도록 해주는 작은 프로그램입니다. 이를 통해 구성한 URDF의 방향은 알맞게 설정되었는지, 원점은 잘 맞는지 등을 확인 가능합니다.
$ ros2 topic echo /joint_states
header:
stamp:
frame_id: ''
name:
- right_wheel_joint
- left_wheel_joint
position:
- 1.918884792812646
- -1.409318464400381
velocity: []
effort: []
---
⇒ right_wheel_joint는 위치 1.9188를 갖고 있으며, 속도와 힘은 제어되고 있지 않고 있습니다.
⇒ left_wheel_joint는 위치 -1.4093를 갖고 있으며, 속도와 힘은 제어되고 있지 않고 있습니다.
이렇게 joint state publisher는 현재 로봇이 가진 움직일 수 있는 모든 joint들을 예의주시하고 topic으로 publish하는 node입니다.
URDF의 joint는 joint state publisher가 담당했다면, robot state publisher는 모든 link와 joint 값을 지속적으로 Subscribe하여 전체 로봇의 구조를 tf 형식으로 publish합니다. 더불어, robot state publisher가 publish하는 /robot_description topic은 rviz에서 로봇의 시각화를 위해 사용되고, gazebo에서 로봇을 등장시키기 위해 사용됩니다.
참고로, ROS 2에서 tf tree를 얻기 위해서는 아래와 같은 커멘드 라인을 사용합니다. (몇 초간 tf listen을 거친 뒤 pdf 형태로 tf tree를 도출해줍니다.)
$ ros2 run tf2_tools view_frames.py
지금까지 배운 내용들을 복습해봅시다.
각 Node간 연결을 rqt_graph를 통해 살펴보면 아래와 같습니다.
description.launch.py를 분석해봅시다.
return LaunchDescription([
joint_state_publisher_gui,
robot_state_publisher,
TimerAction(
period=5.0,
actions=[rviz]
),
])
# Joint State Publisher
joint_state_publisher_gui = Node(
package='joint_state_publisher_gui',
executable='joint_state_publisher_gui',
name='joint_state_publisher_gui'
)
# Prepare Robot State Publisher Params
urdf_file = os.path.join(description_pkg_path, 'urdf', 'fusionbot_description.urdf')
# Robot State Publisher
robot_state_publisher = Node(
package='robot_state_publisher',
executable='robot_state_publisher',
name='robot_state_publisher',
output='screen',
parameters=[{'use_sim_time': True}],
arguments=[urdf_file],
)
# Launch RViz
rviz_config_file = os.path.join(description_pkg_path, 'rviz', 'description.rviz’)
rviz = Node(
package='rviz2',
executable='rviz2',
name='rviz2',
output='screen',
arguments=['-d', rviz_config_file]
)
...
TimerAction(
period=3.0,
actions=[rviz]
),
지금까지의 예시들은 모두 제가 미리 작성해둔 xacro를 사용하였는데요, 그럼 이런 xacro와 urdf는 어떻게 만들 수 있는지, 설계된 모바일 로봇을 바탕으로 Gazebo, ROS와 연동하는 방법에 대해 알아보겠습니다.
이번 예시는 무료 사용이 가능한 Autodesk의 Fusion 360을 사용하였으며, Fusion 360에 Third Party ADD_IN을 추가하여 URDF를 생성하고, 생성된 URDF파일을 사용하여 Rviz 및 Gazebo와 연동하고자 합니다.
사용한 Add In 링크는 다음과 같습니다. ⇒ https://github.com/syuntoku14/fusion2urdf
링크를 통해 FusionBot의 외형을 확인할 수 있습니다. ⇒ https://a360.co/3gdOajr
완성된 로봇으로부터 URDF를 추출하면 첨부파일과 같은 패키지를 얻을 수 있습니다.
ROS 1의 경우 바로 사용이 가능하지만, ROS 2는 몇가지 추가 설정들이 필요합니다. 따라서, 이번 예시에서는 ROS 2 환경을 기준으로 설정을 변경해보면서 URDF와 ROS 연동에 대해 학습해보겠습니다.
$ ros2 pkg create --build-type ament_cmake <package_name>
ros2 pkg create --build-type ament_cmake temp_description
결국 우리가 만들고자 하는 패키지는 제가 배포한 fusionbot_description과 동일합니다. 예제를 따라오시면서 발생하는 문제는 fusionbot_description을 참고하여 해결하시면 됩니다.
urdf 폴더 내부의 파일들을 수정해봅시다.
<?xml version="1.0" ?>
<robot name="fusionbot" xmlns:xacro="http://www.ros.org/wiki/xacro" >
<xacro:property name="body_color" value="Gazebo/Silver" />
<gazebo>
<plugin filename="libgazebo_ros_control.so" name="control"/>
</gazebo>
<gazebo reference="base_link">
<material>${body_color}</material>
<mu1>0.1</mu1>
<mu2>0.1</mu2>
<selfCollide>true</selfCollide>
<gravity>true</gravity>
</gazebo>
<gazebo reference="lidar_1">
<material>${body_color}</material>
<mu1>0.2</mu1>
<mu2>0.2</mu2>
<selfCollide>true</selfCollide>
</gazebo>
<gazebo reference="right_wheel_1">
<material>${body_color}</material>
<mu1>100000.0</mu1>
<mu2>100000.0</mu2>
<selfCollide>true</selfCollide>
</gazebo>
<gazebo reference="left_wheel_1">
<material>${body_color}</material>
<mu1>1500</mu1>
<mu2>1500</mu2>
<selfCollide>true</selfCollide>
</gazebo>
</robot>
libgazebo_ros_control는 ros control의 gazebo 버전입니다. ros control interface를 통해 gazebo 상의 로봇과 실제 로봇의 움직임을 별도의 추가 개발 없이 편리하게 Swap 가능합니다.
mu1, mu2는 마찰력과 관련된 변수로 자세한 설명은 아래 링크를 참고하세요.
=> https://answers.gazebosim.org//question/13083/explain-gazebo-friction-coefficients-mu-and-mu2/
다음으로, 아래 작업들을 수행합니다.
=> fusionbot.trans를 삭제합니다. 해당 파일은 ROS 1에서만 필요한 파일입니다. 이는 ros_control을 gazebo에서 실행할 때 필요한 파일로 ROS 1 시간에 함께 살펴보겠습니다.
=> fusionbot.xacro를 fusionbot.urdf.xacro으로 이름을 변경합니다. (이후 자동 URDF 생성을 위함입니다.)
=> 변경한 fusionbot.urdf.xacro를 수정합니다.
<xacro:include filename="$(find fusionbot_description)/urdf/materials.xacro" />
<!-- <xacro:include filename="$(find fusionbot_description)/urdf/fusionbot.trans" /> -->
<xacro:include filename="$(find fusionbot_description)/urdf/fusionbot.gazebo" />
# From
<joint name="��ü3" type="fixed">
<origin rpy="0 0 0" xyz="0.05 0.0 0.11"/>
<parent link="base_link"/>
<child link="lidar_1"/>
</joint>
<joint name="ȸ��7" type="continuous">
<origin rpy="0 0 0" xyz="0.0 -0.1 0.05"/>
<parent link="base_link"/>
<child link="right_wheel_1"/>
<axis xyz="-0.0 -1.0 0.0"/>
</joint>
<joint name="ȸ��8" type="continuous">
<origin rpy="0 0 0" xyz="0.0 0.1 0.05"/>
<parent link="base_link"/>
<child link="left_wheel_1"/>
<axis xyz="-0.0 1.0 0.0"/>
</joint>
# To
<joint name="lidar_joint" type="fixed">
<origin rpy="0 0 0" xyz="0.05 0.0 0.11" />
<parent link="base_link" />
<child link="lidar_1" />
</joint>
<joint name="right_wheel_joint" type="continuous">
<origin rpy="0 0 0" xyz="0.0 -0.1 0.05" />
<parent link="base_link" />
<child link="right_wheel_1" />
<axis xyz="-0.0 -1.0 0.0" />
</joint>
<joint name="left_wheel_joint" type="continuous">
<origin rpy="0 0 0" xyz="0.0 0.1 0.05" />
<parent link="base_link" />
<child link="left_wheel_1" />
<axis xyz="-0.0 1.0 0.0" />
</joint>
<joint name="right_wheel_joint" type="continuous">
<origin rpy="0 0 0" xyz="0.0 -0.1 0.05" />
<parent link="base_link" />
<child link="right_wheel_1" />
<axis xyz="0.0 1.0 0.0" />
</joint>
<joint name="left_wheel_joint" type="continuous">
<origin rpy="0 0 0" xyz="0.0 0.1 0.05" />
<parent link="base_link" />
<child link="left_wheel_1" />
<axis xyz="0.0 1.0 0.0" />
</joint>
<link name='base_footprint' />
...
<joint name='base_link_joint' type='fixed'>
<origin rpy="0 0 0" xyz="0 0 0" />
<parent link="base_footprint" />
<child link="base_link" />
</joint>
# From
<xacro:include filename="$(find fusionbot_description)/urdf/materials.xacro" />
...
<mesh filename="package://fusionbot_description..." />
# To
<xacro:include filename="$(find temp_description)/urdf/materials.xacro" />
...
<mesh filename="package://temp_description..." />
<?xml version="1.0"?>
<robot name="fusionbot" xmlns:xacro="http://www.ros.org/wiki/xacro">
<material name="silver">
<color rgba="0.700 0.700 0.700 1.000" />
</material>
<material name="white">
<color rgba="1 1 1 0.6" />
</material>
<material name="black">
<color rgba="0 0 0 0.7" />
</material>
<material name="red">
<color rgba="1 0 0 1" />
</material>
<material name="blue">
<color rgba="0 0 0.8 1"/>
</material>
</robot>
## Install
install(DIRECTORY meshes urdf
DESTINATION share/${PROJECT_NAME}
)
**
ament_package()
cd ~/ros2_ws
colcon build --packages-select temp_description
source install/local_setup.bash
find_package(xacro REQUIRED)
# Xacro files
file(GLOB xacro_files urdf/*.urdf.xacro)
foreach(it ${xacro_files})
# remove .xacro extension
string(REGEX MATCH "(.*)[.]xacro$" unused ${it})
set(output_filename ${CMAKE_MATCH_1})
# create a rule to generate ${output_filename} from {it}
xacro_add_xacro_file(${it} ${output_filename})
list(APPEND urdf_files ${output_filename})
endforeach(it)
add_custom_target(media_files ALL DEPENDS ${urdf_files})
⇒ 이 과정이 필요한 이유는 다음과 같습니다.
<?xml version="1.0" ?>
<!-- =================================================================================== -->
<!-- | This document was autogenerated by xacro from /home/kimsooyoung/ros2_ws/src/du2023-gz/test_description/urdf/fusionbot.urdf.xacro | -->
<!-- | EDITING THIS FILE BY HAND IS NOT RECOMMENDED | -->
<!-- =================================================================================== -->
<robot name="fusionbot">
<material name="silver">
<color rgba="0.700 0.700 0.700 1.000"/>
</material>
...
robot state publisher와 joint_state_publisher_gui를 통해 robot_description topic을 생성하고 rviz를 통해 이를 시각화해봅시다.
return LaunchDescription([
joint_state_publisher_gui,
robot_state_publisher,
rviz,
])
## Install
install(DIRECTORY meshes urdf launch rviz
DESTINATION share/${PROJECT_NAME}
)
colcon build --symlink-install --packages-select temp_description
source install/local_setup.bash
ros2 launch temp_description description.launch.py