본문으로 건너뛰기

ROS 2 — 거북이로 직접 해보는 토픽·서비스·액션 (turtlesim)

화면 속 거북이 한 마리로 ROS의 노드·토픽·메시지·서비스·액션을 처음부터 끝까지 직접 따라 해본다. 용어를 하나하나 풀어주고, 명령마다 무엇을 하고 결과가 무슨 뜻인지까지 설명한다.

ROS를 글로만 읽으면 노드가 어떻고 토픽이 어떻고 하는 말이 좀처럼 손에 안 잡힌다. 그래서 ROS에는 turtlesim(터틀심) 이라는 연습용 장난감이 딸려 온다. 화면에 거북이 한 마리가 떠 있고, 그 거북이를 움직이거나 말 거는 방식이 진짜 로봇을 다루는 방식과 똑같다.

이 글은 그 거북이로 ROS의 통신 수단을 전부 한 번씩 직접 써보는 실습이다. 끝까지 따라 하면 이런 걸 하게 된다.

  • 거북이를 화면에 띄운다
  • 거북이를 움직인다 (앞으로 가고 빙글빙글 돌게)
  • 거북이의 현재 위치를 읽어온다
  • 거북이를 한 마리 더 만든다
  • 거북이를 정해진 방향으로 정확히 돌린다

이 다섯 가지를 하면서 자연스럽게 노드 · 토픽 · 메시지 · 서비스 · 액션 이라는 ROS의 핵심 다섯 단어를 다 만난다. 처음 보는 단어가 나와도 그때그때 풀어서 설명하니 걱정하지 않아도 된다.

0. 시작 전에 — 딱 네 가지만 약속하고 가자

본격적으로 들어가기 전에, 처음이면 헷갈리는 네 가지를 미리 짚는다. 이것만 알면 막혀도 당황하지 않는다.

① 명령은 “터미널”에 친다. 터미널은 글자로 컴퓨터에게 명령하는 검은 창이다. 마우스로 누르는 대신 명령어를 한 줄 적고 엔터를 친다. 아래 코드 칸에서 $ 뒤에 있는 게 내가 치는 명령이고, 그 아래 줄들이 컴퓨터가 보여주는 결과다. ($ 기호 자체는 치지 않는다.)

② 새 터미널을 열 때마다 ROS를 “소개”해줘야 한다. 터미널은 처음 열리면 ROS가 뭔지 모른다. 그래서 매번 이 한 줄을 먼저 친다.

$ source /opt/ros/jazzy/setup.bash

source(소스)는 “여기 ROS 있으니까 알아둬” 라고 터미널에게 알려주는 절차다. 이걸 빼먹으면 나중에 ros2: command not found(ros2 라는 명령을 못 찾겠다) 라는 에러가 난다. 그러면 십중팔구 이 줄을 안 친 것이다.

③ 터미널을 여러 개 열게 된다. 프로그램 하나를 켜면 그 터미널은 그 프로그램에 붙잡혀 계속 돌아간다. 그래서 거북이를 띄우는 터미널 하나, 명령을 내리는 터미널 하나… 이렇게 여러 개를 동시에 열어 둔다. 새 터미널을 열면 ②번부터 다시 한다.

④ 멈출 때는 Ctrl + C. 계속 돌아가는 프로그램을 멈추고 싶으면 그 터미널에서 키보드의 CtrlC 를 같이 누른다.

이제 시작하자.

1. 거북이를 화면에 띄우기

첫 번째 터미널을 열고, ROS를 소개해준 다음(약속 ②), 거북이를 띄운다.

$ source /opt/ros/jazzy/setup.bash
$ ros2 run turtlesim turtlesim_node
[INFO] [turtlesim]: Starting turtlesim with node name /turtlesim
[INFO] [turtlesim]: Spawning turtle [turtle1] at x=[5.5] y=[5.5] theta=[0.0]

→ 명령을 친 순간 파란 창이 하나 뜨고, 한가운데에 거북이 한 마리가 나타난다. 터미널에는 위처럼 “거북이를 (5.5, 5.5) 위치에 만들었다” 는 안내가 찍힌다.

✋ 잠깐 — 방금 친 명령 뜯어보기 ros2 run turtlesim turtlesim_nodeturtlesim 이라는 꾸러미 안의 turtlesim_node 라는 프로그램을 실행해줘” 라는 뜻이다. ros2 run [꾸러미 이름] [프로그램 이름] — ROS에서 프로그램 하나를 켜는 가장 기본 명령이다.

이 터미널은 닫지 말고 그대로 둔다. 여기가 거북이를 살아 있게 해주는 터미널이다. (Ctrl+C 를 누르면 거북이가 사라진다.)

2. 이 거북이의 정체 — “노드” 들여다보기

거북이가 떠 있는 동안, 새 터미널을 연다(약속 ②·③를 잊지 말고). 그리고 지금 컴퓨터에 어떤 ROS 프로그램이 돌고 있는지 물어본다.

$ source /opt/ros/jazzy/setup.bash
$ ros2 node list
/turtlesim

/turtlesim 하나가 보인다. 이렇게 돌고 있는 ROS 프로그램 하나를 ROS에서는 노드(node) 라고 부른다. 지금은 거북이 노드 하나만 켜져 있는 것이다.

✋ 잠깐 — 노드(node)란? 노드는 한 가지 일을 맡은 프로그램 한 개다. 진짜 로봇이라면 “카메라를 담당하는 노드”, “바퀴를 굴리는 노드”처럼 일감별로 노드를 나눈다. 지금 우리의 거북이 노드는 거북이를 그리고 움직이는 일을 맡고 있다.

이제 이 거북이 노드가 무엇을 할 수 있는지 통째로 들여다보자. 이게 오늘 실습의 지도가 된다.

$ ros2 node info /turtlesim
/turtlesim
  Subscribers:
    /turtle1/cmd_vel: geometry_msgs/msg/Twist
  Publishers:
    /turtle1/pose: turtlesim/msg/Pose
  Service Servers:
    /clear: std_srvs/srv/Empty
    /spawn: turtlesim/srv/Spawn
  Action Servers:
    /turtle1/rotate_absolute: turtlesim/action/RotateAbsolute

(실제 출력은 더 길지만, 오늘 쓸 것만 추렸다.) 어려운 영어 단어가 보여도 괜찮다. 칸이 네 개로 나뉘어 있다는 것만 보자.

  • Subscribers (구독) — 거북이가 받아서 듣는 것. /turtle1/cmd_vel = “이 속도로 움직여” 라는 명령을 받는다
  • Publishers (발행) — 거북이가 스스로 내보내는 것. /turtle1/pose = “나 지금 여기 있어” 라는 위치 정보를 계속 알린다
  • Service Servers (서비스)한 번 부탁하면 한 번 해주는 것. /spawn = “거북이 한 마리 더 만들어줘”
  • Action Servers (액션)시간이 좀 걸리는 일을 시키는 것. /turtle1/rotate_absolute = “이 방향 보도록 돌아”
거북이 노드 /turtlesim 토픽 — 계속 흐르는 데이터 /turtle1/cmd_vel (받기) · /turtle1/pose (내보내기) 서비스 — 한 번 부탁, 한 번 응답 /spawn · /clear 액션 — 시간 걸리는 일 /turtle1/rotate_absolute

오늘은 이 네 칸을 위에서부터 하나씩 직접 써본다. 거북이 노드 하나가 이 모든 통신 수단을 다 갖고 있다는 게 핵심이다.

3. 토픽으로 거북이 움직이기 (보내기)

가장 먼저 토픽부터. 토픽은 계속 흘러가는 데이터가 지나는 통로다. 비유하면 라디오 방송과 같다.

✋ 잠깐 — 토픽 / 발행 / 구독

  • 토픽(topic) = 데이터가 흐르는 통로의 이름. 라디오의 주파수(예: 89.1 MHz)와 같다
  • 발행(publish) = 그 통로에 데이터를 내보내는 것. 라디오 방송국이 하는 일
  • 구독(subscribe) = 그 통로에서 데이터를 받는 것. 라디오 수신기가 하는 일

방송국과 라디오는 서로를 몰라도, 같은 주파수만 맞추면 소리가 전달된다. ROS도 똑같이, 같은 토픽 이름만 쓰면 데이터가 전달된다.

거북이는 /turtle1/cmd_vel 이라는 토픽을 구독하고 있었다(§2에서 봤다). cmd_velcommand velocity, 즉 “움직임 속도 명령” 이라는 뜻이다. 그러니 우리가 이 토픽에 속도를 발행하면 거북이가 움직인다.

다음 명령을 친다. (조금 길지만 그대로 따라 친다.)

$ ros2 topic pub /turtle1/cmd_vel geometry_msgs/msg/Twist "{linear: {x: 2.0}, angular: {z: 1.0}}"
publisher: beginning loop
publishing #1: geometry_msgs.msg.Twist(linear=...(x=2.0, y=0.0, z=0.0), angular=...(x=0.0, y=0.0, z=1.0))
publishing #2: ...
publishing #3: ...

거북이가 빙글빙글 원을 그리며 움직이기 시작한다! 터미널에는 “내가 이 메시지를 보내고 있어” 하는 줄이 1초에 한 번씩 계속 찍힌다. 다 봤으면 Ctrl+C 로 멈춘다.

명령이 길어 보이지만, 사실 세 토막이다.

  • ros2 topic pub“토픽에 발행(pub = publish)할게”
  • /turtle1/cmd_vel어느 토픽에 보낼지 (거북이의 속도 명령 통로)
  • geometry_msgs/msg/Twist "{linear: {x: 2.0}, angular: {z: 1.0}}"무슨 데이터를 보낼지

✋ 잠깐 — 메시지(message)와 Twist 통로(토픽)에 흘려보내는 데이터 한 덩어리메시지(message) 라고 한다. 그런데 아무 모양이나 보내면 거북이가 못 알아듣는다. 그래서 데이터의 정해진 양식이 있는데, 그게 메시지 타입이다. 여기서 쓴 타입은 Twist(트위스트) — 움직임을 표현하는 표준 양식이다. 두 칸으로 되어 있다.

  • linear(직선) → x = 앞뒤로 가는 속도
  • angular(회전) → z = 제자리에서 도는 속도

그래서 linear.x = 2.0(앞으로 가라) + angular.z = 1.0(동시에 돌아라) = 앞으로 가면서 돈다 = 원이 된다.

✏️ 직접 바꿔보기 위 명령에서 숫자를 바꿔 다시 쳐보자. 거북이가 어떻게 달라질까?

  • angular: {z: 0.0} 으로 하면? → 안 돌고 똑바로 앞으로만 간다
  • linear: {x: 1.0}, angular: {z: 2.0} 으로 하면? → 더 작은 원을 그린다 (도는 게 빨라서)

4. 토픽으로 거북이 위치 읽기 (받기)

방금은 거북이에게 보냈다. 이번엔 거북이가 내보내는받아 본다. 거북이는 자기 위치를 /turtle1/pose 라는 토픽으로 끊임없이 발행하고 있다. (pose(포즈) = 위치와 방향.)

또 새 터미널을 열고(약속 ②), 그 통로를 엿듣는다.

$ source /opt/ros/jazzy/setup.bash
$ ros2 topic echo /turtle1/pose
x: 5.544444
y: 5.544444
theta: 0.0
linear_velocity: 0.0
angular_velocity: 0.0
---
x: 5.544444
y: 5.544444
theta: 0.0
...

→ 거북이의 현재 상태가 계속 찍힌다. 각 줄의 뜻은 이렇다.

  • x, y — 거북이의 현재 좌표 (창의 왼쪽 아래가 0,0 / 한가운데가 약 5.5, 5.5)
  • theta(세타) — 거북이가 바라보는 방향. 단위는 라디안이다 (아래 설명)
  • linear_velocity, angular_velocity — 지금 얼마나 빠르게 직진/회전 중인지

✋ 잠깐 — 라디안(radian)? 우리는 보통 각도를 도(°) 로 잰다 (직각 = 90°). ROS는 대신 라디안이라는 단위를 쓴다. 둘은 그냥 같은 걸 다르게 재는 것뿐이다.

  • 한 바퀴(360°) ≈ 6.28 라디안
  • 반 바퀴(180°) ≈ 3.14 라디안 (원주율 π!)
  • 직각(90°) ≈ 1.57 라디안

echo(에코) 는 메아리처럼 흐르는 내용을 그대로 보여줘 라는 뜻이다. 여기서 멋진 걸 해볼 수 있다 — §3의 움직이는 명령과 이 echo 를 동시에 켜두면, 거북이가 도는 동안 x, y, theta 숫자가 실시간으로 바뀌는 게 보인다. 보내는 쪽과 받는 쪽이 동시에 도는 것이다.

이 위치 메시지의 양식은 turtlesim/msg/Pose 다. 아까 Twist 는 ROS 표준 양식이었는데, 이 Poseturtlesim이 자기만 쓰려고 직접 만든 양식이다. 표준이든 직접 만든 것이든, 토픽 위를 흐른다는 점은 똑같다.

5. 서비스로 거북이 한 마리 더 만들기

토픽은 계속 흐르는 데이터에 어울린다. 그런데 “거북이 한 마리 더 만들어줘” 같은 일은 한 번만 하면 되는, 딱 떨어지는 부탁이다. 이런 건 서비스(service) 로 한다.

✋ 잠깐 — 서비스(service)? 서비스는 자판기와 같다. 버튼을 누르면(→ 요청, Request) 음료가 나온다(→ 응답, Response). 한 번 주고받으면 끝. 토픽처럼 계속 흐르지 않는다. “지금 상태 알려줘”, “설정 바꿔줘”, “하나 만들어줘” 같은 한 번짜리 부탁에 쓴다.

거북이 노드는 /spawn(스폰 = 생성) 이라는 서비스를 갖고 있었다. 새 터미널에서 이걸 호출한다.

$ source /opt/ros/jazzy/setup.bash
$ ros2 service call /spawn turtlesim/srv/Spawn "{x: 2.0, y: 2.0, theta: 0.0, name: 'turtle2'}"
requester: making request: turtlesim.srv.Spawn_Request(x=2.0, y=2.0, theta=0.0, name='turtle2')

response:
turtlesim.srv.Spawn_Response(name='turtle2')

파란 창의 (2, 2) 위치에 거북이가 한 마리 더 생긴다! 그리고 터미널을 보면 오간 대화가 그대로 찍혀 있다.

  • making request: ...(x=2.0, y=2.0, ... name='turtle2') — 내가 보낸 요청: “(2,2)에 ‘turtle2’라는 거북이 만들어줘”
  • response: ...(name='turtle2') — 거북이 노드의 응답: “만들었어, 이름은 turtle2야”

ros2 service call [서비스 이름] [양식] "{내용}" — 한 번 부르고, 답을 받고, 끝난다. 토픽처럼 계속 흐르지 않는 게 핵심이다.

이번 양식 turtlesim/srv/Spawn요청 양식응답 양식둘 다 담고 있다 (.srv 파일). 그래서 토픽 메시지(.msg)와 구분해서 srv 라고 적는다.

✏️ 직접 해보기 — 흔적 지우기 거북이들이 지나간 선이 지저분해졌으면, 가장 단순한 서비스로 싹 지울 수 있다.

$ ros2 service call /clear std_srvs/srv/Empty

Empty(엠프티 = 비어 있음) 는 요청에도 응답에도 내용이 없는 가장 단순한 양식이다. 그냥 “지워줘” 만 부탁하고 끝.

6. 액션으로 거북이를 정확한 방향으로 돌리기

마지막은 액션(action) 이다. “정확히 90도 방향을 보도록 천천히 돌아라” 같은 일은 시간이 좀 걸린다. 도는 동안 얼마나 남았는지 알고 싶고, 마음 바뀌면 취소도 하고 싶다. 이런 일에 액션을 쓴다.

✋ 잠깐 — 액션(action)? 액션은 택배 주문과 같다.

  • 목표(Goal) — “이 주소로 보내줘” (주문)
  • 진행(Feedback) — “지금 배송 중, 절반 왔어요” (중간 알림이 여러 번 온다)
  • 결과(Result) — “도착했습니다” (마지막에 한 번)
  • 게다가 도중에 취소도 할 수 있다.

서비스(자판기)는 즉시 끝나지만, 액션은 오래 걸리는 일을 맡기고 진행 상황을 받아보는 것이다.

거북이 노드의 /turtle1/rotate_absolute(회전 — 절대 방향) 액션을 불러서, 90도(= 1.57 라디안) 방향을 보게 한다. 새 터미널에서:

$ source /opt/ros/jazzy/setup.bash
$ ros2 action send_goal /turtle1/rotate_absolute turtlesim/action/RotateAbsolute "{theta: 1.57}" --feedback
Sending goal:
     theta: 1.57

Goal accepted with ID: f8d8c2...

Feedback:
    remaining: 1.21

Feedback:
    remaining: 0.63

Feedback:
    remaining: 0.05

Result:
    delta: -1.57

Goal finished with status: SUCCEEDED

거북이가 천천히 돌아서 위쪽(90도)을 바라보고 멈춘다. 터미널 출력에 택배 주문의 세 박자가 그대로 보인다.

  • Sending goal: theta: 1.57목표를 보냈다: “1.57 라디안 방향을 봐”
  • Goal accepted — 거북이가 “접수했어요”
  • Feedback: remaining: 1.21 → 0.63 → 0.05진행 알림. 남은 회전량이 점점 0에 가까워진다 (배송 추적!)
  • Result: delta: -1.57결과: 실제로 돈 각도
  • Goal finished with status: SUCCEEDED성공적으로 끝났다

맨 끝의 --feedback“진행 상황도 보여줘” 라는 표시다. 이게 없으면 중간 Feedback 줄들이 안 나오고 결과만 나온다.

이 양식 turtlesim/action/RotateAbsolute목표·진행·결과 세 가지를 담고 있다 (.action 파일). 그래서 msg(1개) · srv(요청+응답 2개) 와 구분해 action 이라고 적는다.

✏️ 직접 바꿔보기 theta 숫자를 바꿔보자. 3.14(반 바퀴 = 왼쪽), 0.0(오른쪽, 처음 방향). 거북이가 매번 그 방향으로 정확히 돌아서 멈추는 걸 확인할 수 있다.

7. 방금 한 걸 한 장으로 정리

한 일친 명령쓴 수단데이터 양식
거북이가 가진 것 보기ros2 node info /turtlesim노드
거북이 움직이기ros2 topic pub .../cmd_vel토픽(보내기)geometry_msgs/msg/Twist
위치 읽기ros2 topic echo .../pose토픽(받기)turtlesim/msg/Pose
거북이 추가ros2 service call /spawn서비스turtlesim/srv/Spawn
정확히 회전ros2 action send_goal .../rotate_absolute액션turtlesim/action/RotateAbsolute

다섯 줄 모두 거북이 노드 하나를 상대로 한 것이다. 하나의 노드가 토픽·서비스·액션을 동시에 가질 수 있다. 그럼 셋 중 뭘 쓸지는 어떻게 정할까? 일의 성격으로 고른다.

  • 데이터가 계속 흐르면토픽 (속도 명령, 위치, 카메라 영상…)
  • 한 번 묻고 한 번 답이면 → 서비스 (상태 물어보기, 설정 바꾸기, 하나 만들기…)
  • 시간이 걸리고 진행 상황·취소가 필요하면 → 액션 (정해진 곳까지 이동, 회전, 물건 집기…)

8. 막히면 — 이 증상이면 이 원인

이런 증상이면십중팔구 원인
ros2: command not found그 터미널에서 source 를 안 함 → source /opt/ros/jazzy/setup.bash 먼저 (약속 ②)
파란 창이 안 뜸화면(디스플레이)이 없는 환경. turtlesim은 GUI가 필요하다
topic pub 했는데 거북이가 가만히토픽 이름을 잘못 침 → ros2 topic list 로 정확한 이름 확인
service call 이 한참 멈춰 있음서비스 이름이 틀림 → ros2 service list 로 확인
action send_goal 이 “server not available”§1의 거북이 터미널이 꺼졌다 → 거북이를 다시 띄우기
명령이 너무 길어 오타가 남천천히, 따옴표(")와 중괄호({ }) 짝이 맞는지 확인

가장 흔한 두 가지는 source 를 빼먹은 것거북이를 안 띄운 채 명령만 친 것이다. 막히면 이 둘부터 의심하자.

한 줄로 박아둘 것

  1. 노드 = 한 가지 일을 맡은 프로그램 한 개. 거북이도 노드 하나다 — ros2 node info 로 그 노드가 가진 통신 수단을 통째로 본다
  2. 토픽 = 계속 흐르는 데이터 통로. topic pub(보내기)으로 거북이를 움직이고, topic echo(받기)로 위치를 읽는다 — 라디오 방송 모형
  3. 서비스 = 한 번 부탁 → 한 번 응답. service call /spawn 으로 거북이를 더 만든다 — 자판기 모형
  4. 액션 = 시간 걸리는 일 + 진행 알림 + 취소. action send_goal 으로 정확히 회전시킨다 — 택배 주문 모형
  5. 메시지 = 그 통로로 흐르는 데이터의 양식. 토픽은 .msg, 서비스는 .srv, 액션은 .action 으로 그 양식을 정한다
  6. 셋 중 뭘 쓸지는 일의 성격으로 고른다 — 계속 흐르면 토픽, 묻고 답이면 서비스, 오래 걸리면 액션