한 줄 요약
ROS2 Humble 환경에서 Doosan E0509 + MoveIt2를 사용해 입력한 목표 좌표들을 순차 이동시키고, /assignment/status 커스텀 메시지로 상태를 통합해 PyQt5 GUI에서 실시간 모니터링/정지까지 구현했다.
1. 요구사항 정리
- 목표 좌표(x,y,z) 1개 이상 입력
- ABS/REL 모드 선택
- vel/acc scaling 반영
- PyQt5 GUI(코드 UI), 버튼 동작은 별도 스레드
- 연결 상태 / 동작 상태 / 로그 / joint / EE pose 표시
2. 전체 구성(아키텍처)
- MoveIt2: /move_action (MoveGroup action)
- ros2_control: dsr_moveit_controller (FollowJointTrajectory)
- 내 노드
- movegroup_sequence: 목표 시퀀스 실행
- assignment_status_node: 상태 통합 publish
- assignment_gui: PyQt5 UI

3. 좌표 입력과 ABS/REL 처리
Targets 입력 포맷:
- "0.35,0.00,0.45;0.30,0.10,0.40"
ABS는 base_link 기준 목표로 사용하고,
REL은 TF로 현재 EE pose를 읽어 offset을 더해 목표를 만든다.
4. MoveIt2 Action으로 이동시키기
핵심은 /move_action에 MoveGroup goal을 보내는 것.
- 기본 성공률을 위해 position-only constraint를 기본값으로 사용
- keep_orientation 옵션을 켜면 pos+ori로 1차 시도하고 실패 시 pos-only로 fallback
- vel/acc scaling을 MotionPlanRequest에 반영

5. 상태관리: /assignment/status 커스텀 메시지
요구사항의 “연결/동작/로그/joint/pose”를 GUI에서 바로 쓰려면 토픽을 분산시키기보다 단일 토픽으로 통합하는 게 편했다.
- 연결상태: server_is_ready()로 /move_action, follow_joint_trajectory 체크
- joint: /joint_states
- EE pose: TF(base_link→link_6)
- 로그: /assignment/state_event로 이벤트를 받고 recent_logs ring buffer로 유지

6. PyQt5 GUI: 코드 UI + 스레드 분리
- 좌측: 입력/옵션/RUN/STOP
- 우측: 상태/조인트/포즈/로그
- RUN은 QThread에서 subprocess로 실행(ROS spin과 UI freeze 방지)
- STOP은 /assignment/stop 호출 + SIGINT로 종료

7. 트러블슈팅: CONTROL_FAILED(-4) 해결
가장 큰 삽질 포인트.
- plan_only=True는 되는데 실행에서 CONTROL_FAILED
- 원인은 controller action server가 0개(컨트롤러 미활성)
- spawner로 dsr_moveit_controller 활성화 후 해결
(한 줄 해결 커맨드)
ros2 run controller_manager spawner dsr_moveit_controller --controller-manager /controller_manager
8. 마무리 / 링크
- GitHub: https://github.com/minoLMW/doosan-e0509-assignment
- 스크린샷: RViz / GUI / SUCCESS 로그 / status echo