Jupyter 노트북을 사용한 프로토타이핑

Jupyter 노트북을 사용한 프로토타이핑#

이 튜토리얼에서는 MoveIt 2 Python API와 함께 Jupyter 노트북을 사용하는 방법을 배웁니다. 이 튜토리얼은 다음과 같은 섹션으로 구성되어 있습니다:

  • 시작하기: 튜토리얼 설정 요구사항에 대한 개요입니다.

  • 실행 파일 이해하기: 실행 파일 명세에 대한 개요입니다.

  • 노트북 설정: 노트북 임포트와 구성입니다.

  • 모션 플래닝 예제: moveit_py API를 사용하여 모션을 계획하는 예제입니다.

  • 원격 조작 예제: moveit_py API를 사용하여 조이스틱으로 로봇을 원격 조작하는 예제입니다.

시작하기#

이 튜토리얼을 완료하려면 MoveIt 2와 해당 튜토리얼이 포함된 colcon 작업 공간을 설정해야 합니다. 이러한 작업 공간을 설정하는 방법에 대한 개요는 시작하기 가이드에 제공되어 있습니다.

작업 공간을 설정한 후에는 다음 명령을 실행하여 이 튜토리얼의 코드를 실행할 수 있습니다(이 튜토리얼의 서보 섹션에는 PS4 듀얼쇼크가 필요합니다. 없는 경우 이 매개변수를 false로 설정하는 것을 고려하십시오):

ros2 launch moveit2_tutorials jupyter_notebook_prototyping.launch.py start_servo:=true
  • 이렇게 하면 이 튜토리얼을 완료하는 데 필요한 노드가 실행됩니다.

  • 중요한 점은 이 튜토리얼의 코드를 실행하기 위해 연결할 수 있는 Jupyter 노트북 서버도 실행된다는 것입니다.

  • 브라우저에서 자동으로 Jupyter 노트북 인터페이스가 열리지 않으면 브라우저에서 http://localhost:8888로 이동하여 노트북 서버에 연결할 수 있습니다.

  • 토큰을 입력하라는 메시지가 표시되는데, 이는 실행 파일을 실행할 때 터미널에 인쇄되며 이 토큰을 입력하여 노트북 서버에 연결할 수 있습니다.

  • 토큰이 포함된 URL도 터미널 출력에 인쇄되므로, 토큰을 수동으로 입력하지 않고도 이 URL을 직접 사용하여 노트북 서버에 연결할 수 있습니다.

이 단계를 완료하면 튜토리얼을 진행할 준비가 된 것입니다. 노트북 코드를 실행하기 전에 노트북 인스턴스를 실행하는 방법을 이해할 수 있도록 실행 파일 명세에 대한 간단한 개요를 제공합니다.

실행 파일 이해하기#

이 튜토리얼에서 사용되는 실행 파일이 다른 튜토리얼과 비교할 때 차이점은 Jupyter 노트북 서버를 시작한다는 것입니다. 우리는 일반적인 실행 파일 코드를 간략히 검토하고 주로 노트북 서버 시작에 초점을 맞출 것입니다.

필요한 패키지 임포트:

import os
import yaml
from launch import LaunchDescription
from launch.actions import ExecuteProcess, DeclareLaunchArgument
from launch.substitutions import LaunchConfiguration
from launch_ros.actions import Node, SetParameter
from ament_index_python.packages import get_package_share_directory
from moveit_configs_utils import MoveItConfigsBuilder

YAML 파일을 로드하기 위한 유틸리티 정의:

def load_yaml(package_name, file_path):
        package_path = get_package_share_directory(package_name)
        absolute_file_path = os.path.join(package_path, file_path)

        try:
                with open(absolute_file_path, 'r') as file:
                return yaml.safe_load(file)
        except EnvironmentError:
                return None

서보 노드를 시작하기 위한 실행 인자 정의:

start_servo = LaunchConfiguration('start_servo')

start_servo_arg = DeclareLaunchArgument(
        'start_servo',
        default_value='false',
        description='Start the servo node.')

MoveIt 구성을 정의합니다. 이 단계는 나중에 노트북을 구성할 때도 중요합니다:

moveit_config = (
        MoveItConfigsBuilder(
                robot_name="panda", package_name="moveit_resources_panda_moveit_config"
        )
        .robot_description(file_path="config/panda.urdf.xacro")
        .trajectory_execution(file_path="config/gripper_moveit_controllers.yaml")
        .moveit_cpp(
                file_path=os.path.join(
                        get_package_share_directory("moveit2_tutorials"),
                        "config",
                        "jupyter_notebook_prototyping.yaml"
                )
        )
        .to_moveit_configs()
)

MoveIt 구성을 정의하면 다음 노드 집합을 시작합니다:

  • rviz_node: 시각화를 위해 rviz2를 시작합니다.

  • static_tf: 월드 프레임과 판다 베이스 프레임 사이의 정적 변환을 발행합니다.

  • robot_state_publisher: 업데이트된 로봇 상태 정보(변환)를 발행합니다.

  • ros2_control_node: 관절 그룹을 제어하는 데 사용됩니다.

rviz_config_file = os.path.join(
        get_package_share_directory("moveit2_tutorials"),
        "config", "jupyter_notebook_prototyping.rviz",
)
rviz_node = Node(
        package="rviz2",
        executable="rviz2",
        name="rviz2",
        output="log",
        arguments=["-d", rviz_config_file],
        parameters=[
                moveit_config.robot_description,
                moveit_config.robot_description_semantic,
        ],
)

static_tf = Node(
        package="tf2_ros",
        executable="static_transform_publisher",
        name="static_transform_publisher",
        output="log",
        arguments=["--frame-id", "world", "--child-frame-id", "panda_link0"],
)

robot_state_publisher = Node(
        package="robot_state_publisher",
        executable="robot_state_publisher",
        name="robot_state_publisher",
        output="both",
        parameters=[moveit_config.robot_description],
)

ros2_controllers_path = os.path.join(
        get_package_share_directory("moveit_resources_panda_moveit_config"),
        "config",
        "ros2_controllers.yaml",
)
ros2_control_node = Node(
        package="controller_manager",
        executable="ros2_control_node",
        parameters=[ros2_controllers_path],
        remappings=[
                ("/controller_manager/robot_description", "/robot_description"),
        ],
        output="both",
)

load_controllers = []
for controller in [
        "panda_arm_controller",
        "panda_hand_controller",
        "joint_state_broadcaster",
]:
        load_controllers += [
                ExecuteProcess(
                cmd=["ros2 run controller_manager spawner {}".format(controller)],
                shell=True,
                output="screen",)
                ]

이러한 각 노드의 설정을 정의한 후에는 Jupyter 노트북 서버를 시작하는 프로세스도 정의합니다:

notebook_dir = os.path.join(get_package_share_directory("moveit2_tutorials"), "src")
start_notebook = ExecuteProcess(
        cmd=["cd {} && python3 -m notebook".format(notebook_dir)],
        shell=True,
        output="screen",
)

서보를 시작하려는 경우 조이와 서보 노드도 정의합니다. 마지막으로 LaunchDescription을 반환합니다:

if start_servo:
        servo_yaml = load_yaml("moveit_servo", "config/panda_simulated_config.yaml")
        servo_params = {"moveit_servo": servo_yaml}

        joy_node = Node(
                package="joy",
                executable="joy_node",
                name="joy_node",
                output="screen",
        )

        servo_node = Node(
                package="moveit_servo",
                executable="servo_node_main",
                parameters=[
                        servo_params,
                        moveit_config.robot_description,
                        moveit_config.robot_description_semantic,
                        moveit_config.robot_description_kinematics,
                ],
                output="screen",
        )

        return LaunchDescription(
                [
                start_servo_arg,
                start_notebook,
                static_tf,
                robot_state_publisher,
                rviz_node,
                ros2_control_node,
                joy_node,
                servo_node,
        ]
        + load_controllers
        )

서보를 시작하지 않는 경우 정의한 다른 모든 노드와 프로세스를 포함하는 실행 설명을 반환합니다:

return LaunchDescription(
        [
        start_servo_arg,
        static_tf,
        robot_state_publisher,
        rviz_node,
        ros2_control_node,
        start_notebook,
        ]
        + load_controllers
        )

노트북 설정#

이제 Jupyter 노트북 서버를 실행했으므로 노트북의 코드 실행을 시작할 수 있습니다. 첫 번째 단계는 필요한 패키지를 임포트하는 것입니다:

import os
import sys
import yaml
import rclpy
import numpy as np

# 메시지 라이브러리
from geometry_msgs.msg import PoseStamped, Pose

# moveit_py
from moveit.planning import MoveItPy
from moveit.core.robot_state import RobotState

# 설정 파일 라이브러리
from moveit_configs_utils import MoveItConfigsBuilder
from ament_index_python.packages import get_package_share_directory

필요한 패키지를 임포트한 후에는 moveit_py 노드 구성을 정의해야 합니다. 이는 다음과 같이 MoveItConfigsBuilder를 사용하여 수행합니다:

moveit_config = (
        MoveItConfigsBuilder(robot_name="panda", package_name="moveit_resources_panda_moveit_config")
        .robot_description(file_path="config/panda.urdf.xacro")
        .trajectory_execution(file_path="config/gripper_moveit_controllers.yaml")
        .moveit_cpp(
                file_path=os.path.join(
                        get_package_share_directory("moveit2_tutorials"),
                        "config",
                        "jupyter_notebook_prototyping.yaml",
                )
        )
        .to_moveit_configs()
).to_dict()

여기서 생성된 구성 인스턴스를 딕셔너리로 변환하여 moveit_py 노드를 초기화하는 데 사용할 수 있습니다. 마지막으로 moveit_py 노드를 초기화합니다:

# rclpy 초기화(로깅 목적으로만)
rclpy.init()

# moveit_py 인스턴스와 panda_arm에 대한 planning 컴포넌트 인스턴스화
panda = MoveItPy(node_name="moveit_py", config_dict=moveit_config)
panda_arm = panda.get_planning_component("panda_arm")

모션 플래닝 예제#

먼저 나중에 계획된 궤적을 계획하고 실행할 때 사용할 도우미 함수를 만듭니다:

def plan_and_execute(
        robot,
        planning_component,
        single_plan_parameters=None,
        multi_plan_parameters=None,
):
        """모션을 계획하고 실행하는 도우미 함수."""
        # 목표로 계획
        if multi_plan_parameters is not None:
                plan_result = planning_component.plan(
                        multi_plan_parameters=multi_plan_parameters
                )
        elif single_plan_parameters is not None:
                plan_result = planning_component.plan(
                single_plan_parameters=single_plan_parameters
        )
        else:
                plan_result = planning_component.plan()

        # 계획 실행
        if plan_result:
                robot_trajectory = plan_result.trajectory
                robot.execute(robot_trajectory, controllers=[])
        else:
                print("Planning failed")

노트북 내에서 간단한 모션의 계획과 실행을 시연할 수 있습니다:

# 미리 정의된 상태를 사용하여 plan start state 설정
panda_arm.set_start_state("ready")

# 미리 정의된 상태를 사용하여 pose goal 설정
panda_arm.set_goal_state(configuration_name = "extended")

# 목표로 계획
plan_and_execute(panda, panda_arm)

우리는 대화식으로 모션 플래닝을 수행할 수 있습니다(모션 플래닝 API의 자세한 내용은 모션 플래닝 튜토리얼 참조). 코드를 개발하다가 다음과 같은 실수를 할 수 있다고 가정해 보겠습니다:

# 미리 정의된 상태를 사용하여 plan start state 설정
panda_arm.set_start_state("ready") # 이는 현재 로봇 구성과 충돌하여 오류가 발생합니다

# 이번에는 pose 메시지를 사용하여 목표 설정
pose_goal = PoseStamped()
pose_goal.header.frame_id = "panda_link0"
pose_goal.pose.orientation.w = 1.0
pose_goal.pose.position.x = 0.28
pose_goal.pose.position.y = -0.2
pose_goal.pose.position.z = 0.5
panda_arm.set_goal_state(pose_stamped_msg = pose_goal, pose_link = "panda_link8")

# 목표로 계획
plan_and_execute(panda, panda_arm)

노트북을 사용하고 있기 때문에 파일을 다시 컴파일할 필요 없이 이 실수를 쉽게 수정할 수 있습니다. 위의 노트북을 아래와 일치하도록 편집하고 셀을 다시 실행하기만 하면 됩니다:

# 현재 상태를 plan start state로 설정
panda_arm.set_start_state_to_current_state()

# 이번에는 pose 메시지를 사용하여 목표 설정
pose_goal = PoseStamped()
pose_goal.header.frame_id = "panda_link0"
pose_goal.pose.orientation.w = 1.0
pose_goal.pose.position.x = 0.28
pose_goal.pose.position.y = -0.2
pose_goal.pose.position.z = 0.5
panda_arm.set_goal_state(pose_stamped_msg = pose_goal, pose_link = "panda_link8")

# 목표로 계획
plan_and_execute(panda, panda_arm)

원격 조작 예제#

사용자는 로봇의 실시간 원격 조작을 수행하고 싶을 수도 있습니다. Python API를 사용하면 모든 프로세스를 종료하고 다시 실행하지 않고도 대화식으로 원격 조작을 시작/중지할 수 있습니다. 이 예제에서는 로봇을 원격 조작하고, 모션 플래닝을 수행한 다음, 다시 로봇을 원격 조작하는 동기 부여 예제를 통해 노트북에서 이것이 어떻게 가능한지 보여줍니다.

이 섹션에서는 moveit_py와 원격 조작을 지원하는 장치가 필요합니다. 여기서는 PS4 듀얼쇼크 컨트롤러를 활용합니다.

로봇 원격 조작을 시작하려면 PS4 듀얼쇼크 컨트롤러를 원격 조작 장치로 인스턴스화합니다.

from moveit.servo_client.devices.ps4_dualshock import PS4DualShockTeleop

# 원격 조작 장치 인스턴스화
ps4 = PS4DualShockTeleop(ee_frame_name="panda_link8")

# 로봇 원격 조작 시작
ps4.start_teleop()

로봇을 기본 구성으로 되돌리기 위해 모션 플래닝을 수행하려면 로봇 원격 조작을 중지하고 아래에 시연된 대로 기존 모션 플래닝 API를 활용하면 됩니다:

# 로봇 원격 조작 중지
ps4.stop_teleop()

# 계획 및 실행
# 미리 정의된 상태를 사용하여 plan start state 설정
panda_arm.set_start_state_to_current_state()

# 미리 정의된 상태를 사용하여 pose goal 설정
panda_arm.set_goal_state(configuration_name = "ready")

# 목표로 계획
plan_and_execute(panda, panda_arm)

이것은 로봇을 기본 구성으로 되돌립니다. 이 구성에서 우리는 다시 한 번 로봇 원격 조작을 시작할 수 있습니다:

ps4.start_teleop()

이 튜토리얼에서는 MoveIt 2 Python API와 Jupyter 노트북을 사용하여 모션 플래닝 및 원격 조작을 수행하는 방법을 살펴보았습니다. Jupyter 노트북은 파일을 다시 컴파일할 필요 없이 빠른 프로토타이핑과 대화식 개발을 가능하게 하므로 로봇 개발에 유용한 도구가 될 수 있습니다.

MoveIt 2와 Python을 사용한 모션 플래닝에 대해 더 알아보려면 MoveIt 2 문서와 예제를 참조하는 것이 좋습니다.