4축 로봇암을 만든 날의 기록 세 번째예요. 이 글은 남은 한 축, 화면의 슬라이더 네 개가 실제 팔을 움직이기까지 데이터가 흐르는 길이에요.
/joint_states → 브리지 → 실물
플래너MoveIt · position_only_ik
로봇을 그리는 데는 노드 두 개면 돼요
ROS 2에서 로봇을 화면에 세우는 최소 구성은 노드 두 개예요. joint_state_publisher가 관절 각도를 /joint_states로 발행하고, robot_state_publisher가 그 각도를 URDF와 결합해요.
URDF는 링크 치수와 관절 축을 적은 로봇 명세 파일인데, robot_state_publisher는 이걸 받아 두 가지를 내보내요.
| 내보내는 것 | 무엇 | RViz가 쓰는 곳 |
|---|---|---|
/robot_description | 로봇의 모양 명세 전체를 실은 문자열 토픽 | 생김새 |
| TF (transform) | “베이스 기준으로 팔꿈치가 어디를 향하는가”의 좌표 변환 트리 | 현재 자세 |
launch 파일 하나에 세 노드를 묶으면 슬라이더로 움직이는 가상 팔이 완성돼요.
Node(package='robot_state_publisher', executable='robot_state_publisher',
parameters=[{'robot_description': xacro_결과}]),
Node(package='joint_state_publisher_gui', executable='joint_state_publisher_gui'),
Node(package='rviz2', executable='rviz2', arguments=['-d', rviz_설정]),여기서 배운 건 로봇의 모양조차 파일이 아니라 토픽으로 흐른다는 점이에요. 구독자가 누구든 같은 명세를 받아 같은 로봇을 그릴 수 있고, 반대로 발행자가 둘이면 남의 로봇이 내 화면에 뜨는 부작용도 같은 원리에서 나와요.
같은 토픽을 실물이 구독하면 텔레옵이 돼요
가상 팔만 움직이면 시뮬레이션이지만, /joint_states를 구독하는 노드를 하나 더 붙이면 텔레옵이 돼요. 브리지 노드가 라디안 각도를 서보 지령(0~180도)으로 환산해 시리얼로 흘리는 구조예요.
이 사슬에서 디버깅을 도운 건 로봇에 달린 OLED였어요. 우노가 수신한 관절 각도 네 개를 상시 표시하게 해 두면, 명령이 사슬의 어디까지 도달했는지를 로봇 몸에서 바로 읽을 수 있어요.
| OLED 숫자 | 팔 | 좁혀지는 구간 |
|---|---|---|
| 안 변함 | — | 통신 구간 (브리지·릴레이·시리얼) |
| 변함 | 안 돎 | 전원이나 서보 |
시리얼 모니터를 열 수 없는 상황(포트를 브리지가 점유 중)에서 화면 밖 관측 지점이 하나 있다는 게 생각보다 큰 차이를 만들어요.
지령 0에서 180이 물리 각도는 아니었어요
사슬이 이어지고 팔이 슬라이더를 따라 움직였는데, 바닥 쪽으로 깊이 내릴수록 화면 속 팔과 실물의 각도가 어긋났어요.
큰 각도로 갈수록 화면과 실물이 벌어짐
오프셋과 스케일, 그리고 조립이 반대로 된 관절의 방향 반전까지 관절당 숫자 두 개가 전부 흡수해요. 위치 센서가 없는 개루프 서보에서는 기준 자세를 사람이 만들어 주고 그 지점의 지령값을 읽는 것, 이게 캘리브레이션의 전부예요. 기준 자세는 수직과 수평이라 폰 수평계 하나로 만들 수 있어요.
플래너도 같은 토픽으로 들어와요
다음 단계로 모션 플래닝 프레임워크인 MoveIt을 올렸어요. 지금까지는 “관절을 몇 도로”라고 직접 말했다면, MoveIt은 “끝점을 이 좌표로”라고 말하면 역기구학과 경로 탐색, 속도 프로파일 계산을 대신해 줘요.
4축이라 끝점의 방향(orientation)까지는 못 맞추고 위치만 풀 수 있어서(position_only_ik), 그리퍼 없이도 성립하는 끝점 터치 과제로 방향을 잡았어요. 계획된 궤적은 모의 컨트롤러가 재생하면서 다시 /joint_states로 흘리기 때문에, 슬라이더 때 만든 브리지가 수정 없이 그대로 실물 전달을 맡아요.
조작 쪽 다음 계획은 키보드 텔레옵의 기능(관절 조그, 포즈 녹화와 재생, 즉시 중단)을 PyQt 슬라이더 펜던트로 옮기는 거예요. 새 기능처럼 보이지만 전부 /joint_states를 발행하거나 구독하는 노드 하나씩이라, 시스템에 더해지는 복잡도는 노드 수만큼만 늘어요.
하루를 통과하고 보니 이 시스템의 뼈대는 결국 토픽 하나예요. 슬라이더도, 화면의 팔도, 실제 팔도, 플래너도, 앞으로 붙일 펜던트와 rosbag도 전부 /joint_states를 사이에 두고 만나요. 부품이 늘어도 사슬이 단순하게 유지되는 이유가 여기 있었어요.
펌웨어 안쪽의 시리얼 프로토콜 문제는 부분 수신과 개루프 서보 부팅 점프 — 시리얼 텔레옵의 함정에, 빌드 경로·가상화·디스커버리 같은 운영 문제는 설치 경로·WSL2 USB·DDS 도메인 — RViz 연동의 운영 함정 셋에 정리했어요.