https://docs.ros.org/en/jazzy/Tutorials/Intermediate/URDF/Using-URDF-with-Robot-State-Publisher.html
Using URDF with robot_state_publisher — ROS 2 Documentation: Jazzy documentation
© Copyright 2025, Open Robotics.
docs.ros.org
background
- 이번 튜토리얼은 걷는 로봇을 모델링하고, 상태를 tf2 메시지로 publish합니다. 그리고 Rviz에서 시뮬레이션을 보는 방법을 보여줍니다.
- 1. 우선 로봇 조립을 위해서 URDF 모델을 생성한다.
- 2. 동작을 시뮬레이션하고 jointState와 변환을 publish하는 노드를 작성한다.
- 3. robot_state_publisher를 사용해서 전체 로봇 상태를 /tf2에 publish한다.
tasks
- 1. 우선 작업할 디렉토리를 만들고 패키지도 만들자.
mkdir -p second_ros2_ws/src
cd second_ros2_ws/src
ros2 pkg create --build-type ament_python --license Apache-2.0 urdf_tutorial_r2d2 --dependencies rclpy
cd urdf_tutorial_r2d2
- 2. assets을 저장할 dir을 만들자. 그리고 해당 디렉토리에는 나중에 아래 2개의 파일이 저장될거임.
- second_ros2_ws/src/urdf_tutorial_r2d2/urdf/r2d2.urdf.xml
- second_ros2_ws/src/urdf_tutorial_r2d2/urdf/r2d2.rviz
mkdir -p urdf
- 3. state를 publish하는 파일을 만들자
- second_ros2_ws/src/urdf_tutorial_r2d2/urdf_tutorial_r2d2/state_publisher.py
- 우리는 로봇의 state를 구체적으로 알수있는 메서드가 필요하다. 그래서 우리는 3개의 모든 joint와 overall odometry를 명세화 시켜야 한다.
from math import sin, cos, pi
import rclpy # ROS 2 Python 클라이언트 라이브러리
from rclpy.node import Node # ROS 2 노드 클래스를 임포트
from rclpy.qos import QoSProfile # Quality of Service 설정을 위한 클래스
from geometry_msgs.msg import Quaternion # 기하학적 회전 정보를 담는 메시지 타입
from sensor_msgs.msg import JointState # 로봇의 조인트 상태를 표현하는 메시지 타입
from tf2_ros import TransformBroadcaster, TransformStamped # 좌표 변환 정보를 퍼블리시하기 위한 클래스
# 로봇의 상태를 퍼블리시하는 노드 클래스 정의
class StatePublisher(Node):
def __init__(self):
rclpy.init() # ROS 2 초기화
super().__init__('state_publisher') # 'state_publisher'라는 이름의 노드 초기화
# QoS (Quality of Service) 설정: 퍼블리시 큐의 깊이를 10으로 설정
qos_profile = QoSProfile(depth=10)
# 조인트 상태를 퍼블리시할 퍼블리셔 생성
self.joint_pub = self.create_publisher(JointState, 'joint_states', qos_profile)
# 좌표 변환 정보를 퍼블리시할 TransformBroadcaster 생성
self.broadcaster = TransformBroadcaster(self, qos=qos_profile)
# 노드 이름 가져오기 및 시작 메시지 출력
self.nodeName = self.get_name()
self.get_logger().info(f"{self.nodeName} started")
# 각도를 라디안으로 변환하는 상수 (1도 = pi/180 라디안)
degree = pi / 180.0
# 루프 속도 설정 (초당 30회 루프 실행)
loop_rate = self.create_rate(30)
# 초기 로봇 상태 설정
tilt = 0.0 # 기울기 초기값
tinc = degree # 기울기 변화량
swivel = 0.0 # 회전 각도 초기값
angle = 0.0 # 로봇이 움직일 원의 각도
height = 0.0 # 로봇의 높이 초기값
hinc = 0.005 # 높이 변화량
# 메시지 객체 선언 및 초기 설정
odom_trans = TransformStamped() # 좌표 변환 메시지 객체
odom_trans.header.frame_id = 'odom' # 부모 프레임 설정
odom_trans.child_frame_id = 'axis' # 자식 프레임 설정
joint_state = JointState() # 조인트 상태 메시지 객체
try:
while rclpy.ok(): # ROS 2 노드가 실행 중일 때 루프 반복
rclpy.spin_once(self) # 콜백 함수가 있다면 한 번 실행 (비동기 처리를 위해)
# 현재 시간 정보 가져오기
now = self.get_clock().now()
# 조인트 상태 메시지 업데이트
joint_state.header.stamp = now.to_msg() # 현재 시간으로 타임스탬프 설정
joint_state.name = ['swivel', 'tilt', 'periscope'] # 조인트 이름 리스트
joint_state.position = [swivel, tilt, height] # 각 조인트의 현재 위치 설정
# 좌표 변환 정보 업데이트
odom_trans.header.stamp = now.to_msg() # 현재 시간으로 타임스탬프 설정
odom_trans.transform.translation.x = cos(angle) * 2 # 원 궤도상의 x 좌표 (반지름 = 2)
odom_trans.transform.translation.y = sin(angle) * 2 # 원 궤도상의 y 좌표
odom_trans.transform.translation.z = 0.7 # 고정된 z 좌표 (높이)
# 로봇의 회전 정보 (롤, 피치, 요)를 쿼터니언으로 변환하여 설정
odom_trans.transform.rotation = euler_to_quaternion(0, 0, angle + pi/2)
# 퍼블리셔를 통해 메시지 전송
self.joint_pub.publish(joint_state) # 조인트 상태 퍼블리시
self.broadcaster.sendTransform(odom_trans) # 좌표 변환 정보 퍼블리시
# 로봇 상태 업데이트 (애니메이션 효과를 위해 주기적으로 값 변경)
tilt += tinc # 기울기 증가
if tilt < -0.5 or tilt > 0.0: # 기울기 범위를 벗어나면 방향 전환
tinc *= -1
height += hinc # 높이 증가
if height > 0.2 or height < 0.0: # 높이 범위를 벗어나면 방향 전환
hinc *= -1
swivel += degree # 회전 각도 증가
angle += degree / 4 # 원 궤도상의 각도 증가
loop_rate.sleep() # 루프 주기 맞추기 위해 대기
except KeyboardInterrupt: # Ctrl+C로 프로그램 종료 시 처리
pass
# 오일러 각(roll, pitch, yaw)을 쿼터니언으로 변환하는 함수
def euler_to_quaternion(roll, pitch, yaw):
qx = sin(roll/2) * cos(pitch/2) * cos(yaw/2) - cos(roll/2) * sin(pitch/2) * sin(yaw/2)
qy = cos(roll/2) * sin(pitch/2) * cos(yaw/2) + sin(roll/2) * cos(pitch/2) * sin(yaw/2)
qz = cos(roll/2) * cos(pitch/2) * sin(yaw/2) - sin(roll/2) * sin(pitch/2) * cos(yaw/2)
qw = cos(roll/2) * cos(pitch/2) * cos(yaw/2) + sin(roll/2) * sin(pitch/2) * sin(yaw/2)
return Quaternion(x=qx, y=qy, z=qz, w=qw) # 변환된 쿼터니언 반환
# 메인 함수: 노드 실행 시작
def main():
node = StatePublisher() # StatePublisher 노드 객체 생성
if __name__ == '__main__':
main() # 메인 함수 실행
- 4. launch file도 생성하자.
- second_ros2_ws/src/urdf_tutorial_r2d2/launch/demo_launch.py
import os
from ament_index_python.packages import get_package_share_directory # 패키지의 공유 디렉토리 경로를 얻기 위한 모듈
from launch import LaunchDescription # Launch 파일을 정의하는 클래스
from launch.actions import DeclareLaunchArgument # 런치 아규먼트(인자)를 선언하는 클래스
from launch.substitutions import LaunchConfiguration # 런치 아규먼트를 런타임에 사용할 수 있도록 설정
from launch_ros.actions import Node # ROS 2 노드를 실행하기 위한 클래스
# Launch 파일을 생성하는 함수
def generate_launch_description():
# 'use_sim_time'이라는 런치 아규먼트를 선언하고 기본값을 'false'로 설정
# 이는 시뮬레이션(Gazebo) 시간 사용 여부를 결정함
use_sim_time = LaunchConfiguration('use_sim_time', default='false')
# URDF 파일 이름 설정
urdf_file_name = 'r2d2.urdf.xml'
# URDF 파일의 경로를 얻기 위해 패키지 디렉토리를 찾음
urdf = os.path.join(
get_package_share_directory('urdf_tutorial_r2d2'), # 패키지 이름
urdf_file_name # 파일 이름
)
# URDF 파일을 읽어 로봇 설명을 변수에 저장
with open(urdf, 'r') as infp:
robot_desc = infp.read()
# LaunchDescription 객체를 반환하여 런치 파일 정의
return LaunchDescription([
# 'use_sim_time' 런치 아규먼트 선언
DeclareLaunchArgument(
'use_sim_time',
default_value='false',
description='Use simulation (Gazebo) clock if true' # 시뮬레이션 시간 사용 여부 설명
),
# robot_state_publisher 노드 실행
Node(
package='robot_state_publisher', # 노드가 포함된 패키지 이름
executable='robot_state_publisher', # 실행 파일 이름
name='robot_state_publisher', # 노드 이름 지정
output='screen', # 노드의 출력 결과를 화면에 표시
parameters=[ # 노드에 전달할 파라미터 설정
{'use_sim_time': use_sim_time, 'robot_description': robot_desc}
],
arguments=[urdf] # URDF 파일 경로를 인자로 전달
),
# state_publisher 노드 실행
Node(
package='urdf_tutorial_r2d2', # 노드가 포함된 패키지 이름
executable='state_publisher', # 실행 파일 이름
name='state_publisher', # 노드 이름 지정
output='screen' # 노드의 출력 결과를 화면에 표시
),
])
- 5. setup.py를 아래와 같이 수정
from setuptools import find_packages, setup
import os
from glob import glob
package_name = 'urdf_tutorial_r2d2'
setup(
name=package_name,
version='0.0.0',
packages=find_packages(exclude=['test']),
data_files=[
('share/ament_index/resource_index/packages',
['resource/' + package_name]),
('share/' + package_name, ['package.xml']),
(os.path.join('share', package_name, 'launch'), glob(os.path.join('launch', '*launch.[pxy][yma]*'))),
(os.path.join('share', package_name), glob('urdf/*')),
],
install_requires=['setuptools'],
zip_safe=True,
maintainer='jihong',
maintainer_email='jihong@todo.todo',
description='TODO: Package description',
license='Apache-2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'state_publisher = urdf_tutorial_r2d2.state_publisher:main'
],
},
)
~
- 6. 패키지 설치 후 적용 및 실행
cd second_ros2_ws
colcon build --symlink-install --packages-select urdf_tutorial_r2d2
source install/setup.bash
ros2 launch urdf_tutorial_r2d2 demo_launch.py
rviz2 -d second_ros2_ws/install/urdf_tutorial_r2d2/share/urdf_tutorial_r2d2/r2d2.rviz'ROS 2' 카테고리의 다른 글
| ROS2 turtlebot4을 gazebo sim에 띄워보기 (0) | 2025.02.23 |
|---|---|
| Launch file (0) | 2025.02.05 |
| tf2 : Introducing tf2 (0) | 2025.02.04 |
| URDF : Using Xacro to clean up your code (0) | 2025.02.04 |
| URDF : Adding physical and collision properties (0) | 2025.02.04 |