Moveit 패키지로 g1 로봇의 속도를 최고 속도로 동작 제어하는 방법

안녕하세요? g1_interactive_controller_gui_v11.py 첨부한 코드를 활용해서 moveit으로 관절 동작 제어를 하고 있습니다. 269 ~ 272 번째 줄에서 max_velocity 값과 arm.max_acceleration 값을 1.0으로 변경한 뒤에 동작 제어를 실행해도, g1_greeting.py 코드에서 arm_client = G1ArmActionClient()로 동작을 시키는 것보다 동작 제어 속도가 현저히 느리더라구요.


        for arm in [self.left_arm, self.right_arm, self.both_arms, self.waist]:
            arm.max_velocity = 1.0
            arm.max_acceleration = 1.0
            arm.allowed_planning_time = 0.3

지금, moveit 제어 동작 또는 G1ArmActionClient 활용 동작이 가능한데, 두 코드는 동시에 진행하면 회전각도 q 값 전달이 충돌되어 오작동이 발생할 것으로 예상됩니다. 그래서 하나의 동작 제어 방식으로 통합하려는데, G1ArmActionClient은 사전에 정의된 동작만 가능하여 moveit 제어 동작으로 통합하려합니다.

결국 질문의 요지는 moveit 제어 동작을 해야하는데, arm_client = G1ArmActionClient() 이것 보다 동작 속도가 느려서 moveit 동작 속도,

questions.zip (34.5 KB)

planning 속도 설정 외에 동작 제어 속도를 높일 수 있는 다른 대안이 있는지 문의드립니다.

1개의 좋아요

또 다른 방법으로는, moveit 제어는 켜 둔 상태에서, G1ArmActionClient를 실행할때, g1_29dof_arm7_ys_waist.py, joint_to_Float32_29dof_waist.py에서 moveit에서 발행한 joint state를 구독하여 self.low_cmd.motor_cmd로 로봇에 전달하는 코드를 비활성화하거나 pause 한 뒤에 G1ArmActionClient 동작이 끝나면 다시 q 값을 전달하는 상태로 전환하는 방법이 있을 것 같은데 코드를 구현해봤는데,

moveit 제어 동작 또는 G1ArmActionClient 활용 동작이 가능한데, 두 코드는 동시에 진행하면 회전각도 q 값 전달이 충돌되어 오작동이 발생하더라구요. 혹시 g1_29dof_arm7_ys_waist.py, joint_to_Float32_29dof_waist.py 이 코드로 moveit에서 발행하는 joint_state 토픽을 구독해서 q 값을 계속 전달하는 상태를 pause 할 수 있는 방법은 없을까요

questions3.zip (44.3 KB)

?

해당 테스트 방법은 face_recognition_node_v2.py 코드에서 얼굴 인식 후에 G1ArmActionClient로 동작을 호출하는데, 호출하기 전에 g1_29dof_arm7_ys_waist.py에서 아래 코드로 self.low_cmd.motor_cmd[G1JointIndex.kNotUsedJoint].q = 0로 비활성화하는 코드를 추가했습니다. 그런데도, 오동작이 발생하더라고요.

    def LowCmdWrite(self):
        self.time_ += self.control_dt_

        if self.paused:
            # Pause 상태: arm_sdk 비활성화 → 로봇 내장 컨트롤러(G1ArmActionClient)가 제어
            self.low_cmd.motor_cmd[G1JointIndex.kNotUsedJoint].q = 0
            self.low_cmd.crc = self.crc.Crc(self.low_cmd)
            self.arm_sdk_publisher.Write(self.low_cmd)
            return
1개의 좋아요

첨부해주신 코드와 상황을 종합해 볼 때, MoveIt의 동작 속도가 G1ArmActionClient보다 느린 이유는 단순히 파라미터 설정의 문제라기보다 경로 생성 알고리즘의 특성실행 인터페이스의 차이 때문일 가능성이 큽니다.

MoveIt은 장애물 회피를 고려한 복잡한 ‘Planning’ 과정을 거치기 때문에, 직선적인 관절 제어를 수행하는 액션 클라이언트보다 보수적이고 느리게 움직이도록 설계되어 있습니다. 속도를 높이기 위한 몇 가지 실질적인 대안을 제안해 드립니다.


1. MoveIt 속도 제한 해제 및 스케일링 설정

코드에서 max_velocitymax_acceleration을 1.0으로 설정하셨음에도 속도가 느리다면, MoveIt 내부의 Velocity/Acceleration Scaling Factor가 기본값(보통 0.1)으로 묶여 있을 수 있습니다.

# MoveGroupCommander 객체(self.left_arm 등)에 직접 스케일링 적용
for arm in [self.left_arm, self.right_arm, self.both_arms, self.waist]:
    arm.set_max_velocity_scaling_factor(1.0)     # 최대 속도의 100% 사용
    arm.set_max_acceleration_scaling_factor(1.0) # 최대 가속도의 100% 사용

주의: 1.0은 하드웨어가 허용하는 최대치이므로, 테스트 시에는 0.5 정도부터 점진적으로 올리는 것을 권장합니다.

2. OMPL Planner 교체 (RRT-Connect 등)

MoveIt의 기본 플래너는 경로의 최적성보다는 '성공 여부’에 집중합니다. RRTConnect 같은 알고리즘은 경로를 빨리 찾지만 최적화되지 않아 느리게 보일 수 있습니다. 좀 더 매끄러운 동작을 원하신다면 플래너를 변경해 보세요.

arm.set_planner_id("RRTstar") # 혹은 "TRRT"

3. 타임 파라미터 재조정 (Time Parameterization)

MoveIt은 생성된 경로에 시간 정보를 입히는 과정을 거칩니다. 이 과정에서 가속도 제한 때문에 속도가 죽는 경우가 많습니다. TOTG(Time Optimal Trajectory Generation) 알고리즘을 사용 중인지 확인해 보시고, 하드웨어의 joint_limits.yaml 파일 내의 max_velocity 값이 너무 낮게 설정되어 있지는 않은지 확인이 필요합니다.

4. Cartesian Path(직선 보간) 활용

관절 공간(Joint Space) 플래닝이 아닌, 현재 위치에서 목표 위치까지 직선으로 이동하는 compute_cartesian_path를 사용하면 중간 연산 과정을 줄이고 더 직관적이며 빠른 동작이 가능합니다.

arm_sdk_publisher.Write(self.low_cmd)가 계속 호출되고 있다면, 설령 특정 플래그를 0으로 보낸다 하더라도 low_cmd 내의 다른 관절값(q)들이 여전히 MoveIt에서 받은 마지막 상태를 유지하며 로봇 컨트롤러에 "위치 명령"으로 전달되고 있을 것입니다. 즉, G1ArmActionClient가 보내는 명령과 arm_sdk_publisher가 보내는 명령이 로봇의 모터 제어기 레벨에서 **경합(Conflict)**을 일으키는 것입니다.

이를 완벽하게 Pause 하기 위한 가장 확실한 방법 3가지를 제안해 드립니다.


방법 1: Write 자체를 중단하고 토픽 발행 중지 (가장 추천)

단순히 특정 값을 0으로 만드는 것이 아니라, paused 상태일 때는 arm_sdk_publisher.Write 호출 자체를 건너뛰어야 합니다. 하지만 Unitree SDK 구조상 연결을 유지해야 할 수도 있으므로, 제어권 전환 시에는 메시지 전송을 멈추는 것이 핵심입니다.

def LowCmdWrite(self):
    self.time_ += self.control_dt_

    if self.paused:
        # 1. 아예 Write를 호출하지 않음으로써 SDK 수준의 명령 충돌 방지
        # 2. 로봇 내장 컨트롤러가 자유롭게 제어할 수 있도록 제어권을 넘김
        return  # 함수를 여기서 종료하여 하단 Write 로직 실행 방지

    # ... 기존의 q값 계산 및 Write 로직 ...
    self.low_cmd.crc = self.crc.Crc(self.low_cmd)
    self.arm_sdk_publisher.Write(self.low_cmd)

방법 2: ROS Service를 이용한 동적 Pause (런타임 제어)

face_recognition_node_v2.py에서 동작을 호출하기 직전에 g1_29dof_arm7_ys_waist.py 노드에 "잠시 멈춰(Pause)"라고 명령을 내리는 서비스 서버를 구현하는 방법입니다.

  1. g1_29dof_arm7_ys_waist.py 내부에 추가:
from std_srvs.srv import SetBool

# ... __init__ 내부 ...
self.pause_service = rospy.Service('pause_moveit_stream', SetBool, self.handle_pause)

def handle_pause(self, req):
    self.paused = req.data
    status = "Paused" if self.paused else "Resumed"
    return {'success': True, 'message': f"MoveIt stream {status}"}

  1. face_recognition_node_v2.py에서 호출:
def call_g1_action(self):
    # 1. MoveIt 스트리밍 일시 정지 요청
    rospy.wait_for_service('pause_moveit_stream')
    pause_proxy = rospy.ServiceProxy('pause_moveit_stream', SetBool)
    pause_proxy(True)

    # 2. G1ArmActionClient 동작 수행
    self.arm_client.greet() 

    # 3. 동작 완료 후 다시 MoveIt 스트리밍 재개
    pause_proxy(False)

방법 3: 가속도/속도 값이 0인 “Null 명령” 전달 (SDK 제약 시)

만약 SDK 통신 세션이 끊기면 안 되는 구조라면, paused일 때 모든 관절의 kp (Stiffness)와 kd (Damping) 값을 0으로 만들어 로봇이 MoveIt의 명령을 무시하고 외부 명령(G1ArmActionClient)에 순응하게 만들 수 있습니다.

if self.paused:
    for motor in self.low_cmd.motor_cmd:
        motor.q = 0 # 의미 없음
        motor.kp = 0 # 제어 강도를 0으로 만들어 명령 무시
        motor.kd = 0
        motor.tau = 0
    self.low_cmd.crc = self.crc.Crc(self.low_cmd)
    self.arm_sdk_publisher.Write(self.low_cmd)
    return

요약 및 조언

현재 오동작이 발생하는 주된 이유는 LowCmdWrite가 루프를 돌며 계속 low_cmd를 쓰고 있기 때문입니다.

**방법 2(ROS Service)**를 사용해 얼굴 인식 노드와 제어 노드 간에 명확한 '제어권 전환 시그널’을 주고받는 것이 가장 깔끔하며, 이때 paused 로직 안에서는 방법 1처럼 Write를 아예 수행하지 않거나 방법 3처럼 게인(kp, kd)을 0으로 설정하는 것을 권장합니다.

이렇게 하면 G1ArmActionClient가 동작하는 동안 MoveIt의 데이터가 로봇으로 전달되는 것을 물리적으로 차단할 수 있습니다.

3번에 대한 답변 : 해당 joint_limits.yaml 설정 값은 속도 제어에 작은 값일까요?

# joint_limits.yaml allows the dynamics properties specified in the URDF to be overwritten or augmented as needed

# For beginners, we downscale velocity and acceleration limits.
# You can always specify higher scaling factors (<= 1.0) in your motion requests.  # Increase the values below to 1.0 to always move at maximum speed.
default_velocity_scaling_factor: 0.1
default_acceleration_scaling_factor: 0.1

# Specific joint properties can be changed with the keys [max_position, min_position, max_velocity, max_acceleration]
# Joint limits can be turned off with [has_velocity_limits, has_acceleration_limits]
joint_limits:
  left_elbow_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  left_shoulder_pitch_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  left_shoulder_roll_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  left_shoulder_yaw_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  left_wrist_pitch_joint:
    has_velocity_limits: true
    max_velocity: 22.0
    has_acceleration_limits: false
    max_acceleration: 0
  left_wrist_roll_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  left_wrist_yaw_joint:
    has_velocity_limits: true
    max_velocity: 22.0
    has_acceleration_limits: false
    max_acceleration: 0
  right_elbow_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  right_shoulder_pitch_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  right_shoulder_roll_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  right_shoulder_yaw_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  right_wrist_pitch_joint:
    has_velocity_limits: true
    max_velocity: 22.0
    has_acceleration_limits: false
    max_acceleration: 0
  right_wrist_roll_joint:
    has_velocity_limits: true
    max_velocity: 37.0
    has_acceleration_limits: false
    max_acceleration: 0
  right_wrist_yaw_joint:
    has_velocity_limits: true
    max_velocity: 22.0
    has_acceleration_limits: false
    max_acceleration: 0
  waist_yaw_joint:
    has_velocity_limits: true
    max_velocity: 32.0
    has_acceleration_limits: false
    max_acceleration: 0

questions4.zip (9.3 KB)

해당 코드와 같이 얼굴 인식 후 동작 실행 시 pause 값을 True로 업데이트하고 topic으로 전달했습니다. 그리고g1_29dof_arm7_ys_waist.py 코드에서 Write 자체를 중단하는 방식으로 구현하니, 경합이 되어 충돌되는 문제는 없어졌습니다.

해당 코드에 문제가 없는지 혹시 크로스 체크를 부탁드려도 될까요? 양해 부탁드립니다 ㅠㅠ

그리고 ROS Service를 이용한 동적 Pause (런타임 제어) 방법은 제가 아직 ROS Service에 대한 구현 지식이 없어서 Topic으로 pause에 대한 정보를 주고 받는 식으로 했는데 이렇게 구현해도 문제가 없을지 질문드립니다.

joint_limits.yaml 설정 값은 사용자의 요구 사항에 맞추어 테스트를 거쳐 결정해야 합니다.
안전을 위해 보수적으로 테스트 하실 것을 권장드립니다.

코드 공유 하실때는 github를 이용해주시면 감사하겠습니다.

주의사항 및 개선 제안

1. 허리(Waist) 잠금 확인

코드 주석에 WaistRoll, WaistPitch가 G1 23/29 DOF 모델에서 무효할 수 있다고 되어 있습니다. 만약 로봇의 허리가 잠겨 있는 모델이라면 해당 관절에 무리한 토크가 걸리지 않도록 arm_joints 리스트에서 제외하거나 Kp 값을 낮게 설정해야 합니다.

2. 통신 주기 정합성

  • control_dt_ = 0.02 (50Hz)로 설정되어 있습니다.
  • MoveIt에서 퍼블리시하는 주기가 이보다 느리면 계단 현상이 발생할 수 있고, 너무 빠르면 데이터 유실이 생길 수 있습니다. 50Hz~100Hz 사이에서 로봇의 떨림을 확인하며 조정하세요.

3. 안전장치 (Safety)

현재 코드에는 목표 각도가 너무 클 경우 차단하는 Joint Limit Check 로직이 없습니다.

Python# 예시: 안전을 위한 각도 제한 (Rad 단위) self.stand_up_joint_pos = np.clip(self.stand_up_joint_pos, -1.5, 1.5)

위와 같이 np.clip을 사용하여 하드웨어 한계를 넘지 않도록 보호하는 코드를 추가하는 것을 권장합니다.


구현 방식 크로스 체크 (Review)

긍정적인 부분

  1. 경합(Conflict) 방지: 두 노드가 동시에 같은 모터에 DDS 명령을 보내면 로봇이 떨리거나(Jitter) 예상치 못한 동작을 하는데, 한쪽의 Write를 멈춤으로써 이를 완벽히 해결했습니다.
  2. 상태 피드백 활용: 얼굴 인식 노드에서 단순히 pause를 보내고 끝내는 것이 아니라, _wait_for_pause_state를 통해 로봇 제어 노드가 실제로 멈췄는지 확인(Confirm)하는 과정은 매우 안정적인 설계입니다.

주의 및 보완 필요 사항

  • Write 중단 시의 상태: Write를 아예 안 하게 되면, 로봇 하드웨어 입장에서는 명령이 끊긴 상태가 됩니다. G1 로봇의 컨트롤러 설정에 따라 명령이 일정 시간 안 들어오면 **Safety Mode(통신 절단으로 간주)**로 진입하여 관절 힘이 풀려버릴 수 있습니다.

  • 해결책: g1_29dof_arm7_ys_waist.py에서 Write를 멈추기 전, 마지막 상태의 kp, kd를 0으로 만들거나 아주 낮은 유지 토크만 주도록 설정하는 것이 안전합니다. (현재는 인사 동작 노드가 제어권을 이어받으므로 큰 문제는 없을 것입니다.)

  • 복구 시점의 연속성: pauseFalse가 되어 다시 MoveIt 제어로 돌아갈 때, self.time_ 변수를 초기화하여 **Stage 1(Smooth transition)**부터 다시 시작하게 유도하는 것이 좋습니다. 그렇지 않으면 로봇이 현재 위치에서 MoveIt의 목표 위치로 갑자기 튀어 나갈 수 있습니다.


Topic vs Service: 어떤 것이 더 좋을까요?

결론부터 말씀드리면, 현재처럼 Topic으로 구현하셔도 전혀 무방합니다.

구분 Topic (현재 방식) Service (제안 방식)
특성 비동기 (Fire-and-forget) 동기 (Request-Response)
장점 구조가 간단하고 확장성이 좋음. 여러 노드가 동시에 상태를 관찰 가능. 명령이 확실히 전달되었는지 시스템 차원에서 보장함.
단점 네트워크 지연 시 응답 확인을 위해 추가 로직 필요. 응답이 올 때까지 대기(Blocking)가 발생할 수 있음.

왜 Topic 방식도 괜찮은가요?

로봇 제어에서는 "현재 로봇이 일시정지 상태인가?"라는 상태 정보가 중요합니다. Topic은 이 상태를 지속적으로 브로드캐스팅하기에 적합합니다. 이미 _wait_for_pause_state 로직을 통해 서비스의 장점인 ‘확인’ 절차를 직접 구현하셨기 때문에 서비스로 바꾼다고 해서 드라마틱한 성능 향상이 있지는 않습니다.


추천하는 개선 코드 (g1_29dof_arm7_ys_waist.py)

제어 노드가 다시 시작할 때 부드럽게 복구되도록 pause 처리 로직에 self.time_ 초기화를 추가하는 것을 추천합니다.

# g1_29dof_arm7_ys_waist.py 내부 수정 제안
def pause_callback(self, msg):
    self.paused = msg.data
    if self.paused:
        self.get_logger().info("Control paused. Waiting for manual action...")
    else:
        # 일시정지가 풀릴 때 다시 Stage 1(부드러운 전환)부터 시작하도록 리셋
        self.time_ = 0.0 
        self.get_logger().info("Control resumed. Smoothing transition...")

def LowCmdWrite(self):
    if self.paused:
        # Write를 하지 않고 상태만 업데이트하거나, 
        # 하드웨어 세이프티를 위해 최소한의 Keep-alive 패킷만 보낼 수도 있습니다.
        return 
    
    # ... 기존 제어 로직 ...