1. 개발 도구 설치
먼저 ROS 2 Python 패키지 개발에 필요한 도구를 설치합니다.
sudo apt update && sudo apt install -y \
python3-flake8-docstrings \
python3-pip \
python3-pytest-cov \
ros-dev-tools



sudo apt install -y \
python3-flake8-blind-except \
python3-flake8-builtins \
python3-flake8-class-newline \
python3-flake8-comprehensions \
python3-flake8-deprecated \
python3-flake8-import-order \
python3-flake8-quotes \
python3-pytest-repeat \
python3-pytest-rerunfailures



설치가 완료되었으면 작업용 워크스페이스를 생성합니다.
2. ROS 2 워크스페이스 생성
ROS 2 작업은 보통 워크스페이스 단위로 관리합니다.
mkdir -p ~/ros2_study/src
cd ~/ros2_study
여기서 구조는 다음과 같습니다.
ros2_study
└── src
src 폴더는 직접 작성하는 패키지들이 들어가는 공간입니다.

3. 빈 워크스페이스 빌드
아직 패키지가 없어도 워크스페이스를 먼저 빌드할 수 있습니다.
colcon build

빌드 후에는 다음 폴더들이 생성됩니다.
build
install
log
src
각 폴더의 역할은 다음과 같습니다.
build : 빌드 중간 파일 저장
install : 실행 가능한 결과물과 환경 설정 파일 저장
log : 빌드 로그 저장
src : 사용자가 작성한 패키지 저장
ROS 2에서 중요한 폴더는 특히 src와 install입니다.
tee 명령이 실행되지 않을 경우에는 아래의 명령으로 설치해야 합니다.
sudo apt install tree

4. Python 패키지 생성
이제 src 폴더로 이동합니다.
cd ~/ros2_study/src/
아래의 명령을 실행하여 Python 패키지를 생성합니다.
ros2 pkg create --build-type ament_python --node-name my_first_node my_first_package
첫 번째 옵션은 Python 패키지를 설정하는 것입니다.
--build-type ament_python
두 번째 옵션은 패키지 이름을 my_first_node로 정의합니다.
--node-name my_first_node
간단한 패키지 생성 명령은 아래와 같습니다.
ros2 pkg create --build-type ament_python <package_name>



test_flake8.py, test_pep257.py, test_copyright.py 파일들은 ROS 2에서 Python 패키지를 만들 때 자동으로 생성되는 코드 품질 검사용 테스트 파일입니다.
아마 패키지를 만들 때 이런 명령을 사용하셨을 가능성이 큽니다.
ros2 pkg create my_first_package --build-type ament_python
그러면 ROS 2가 기본 템플릿으로 test/ 폴더를 만들고 아래 파일들을 자동 생성합니다.
test/
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
각 파일 의미는 다음과 같습니다.
| test_flake8.py | Python 코드 스타일 검사 |
| test_pep257.py | Python docstring 규칙 검사 |
| test_copyright.py | 저작권 문구 검사 |
flake8은 Python 코드가 PEP8 스타일을 잘 지키는지 검사합니다. 예를 들면 들여쓰기, 줄 길이, 사용하지 않는 import 같은 것을 확인합니다.
pep257은 함수나 클래스에 작성하는 문서 문자열, 즉 docstring 형식을 검사합니다.
예:
def add(a, b):
"""Return the sum of a and b."""
return a + b
test_copyright.py는 소스 파일에 copyright 문구가 들어있는지 검사합니다.
이 파일들은 실행에 꼭 필요한 파일은 아닙니다.
즉, ROS 2 노드를 실행하는 데 직접 필요한 파일은 아닙니다.
ros2 run my_first_package my_first_node
이런 실행에는 보통 영향이 없습니다.
하지만 colcon test를 실행하면 사용됩니다.
colcon test
이때 test_flake8.py, test_pep257.py, test_copyright.py가 실행되면서 코드 스타일을 검사합니다.
삭제해도 됩니다.
학습용이나 간단한 실습이면 삭제해도 큰 문제 없습니다.
rm -rf test/
다만 package.xml에 테스트 관련 의존성이 남아 있을 수 있습니다.
예를 들면:
<test_depend>ament_copyright</test_depend>
<test_depend>ament_flake8</test_depend>
<test_depend>ament_pep257</test_depend>
<test_depend>python3-pytest</test_depend>
test/ 폴더를 삭제했다면 이 부분도 지워도 됩니다.
중요한 파일은 다음입니다.
package.xml
패키지 이름, 버전, 의존성 정보를 관리합니다.
setup.py
Python 패키지의 설치 및 실행 엔트리 포인트를 관리합니다.
my_first_node.py
실제로 실행될 Python 노드 파일입니다.
__init__.py
해당 폴더를 Python 패키지로 인식하게 해주는 파일입니다.
5. VS Code 실행
~/ros2_study/src에서 아래의 명령을 실행하여 VS Code를 실행합니다.
code .
여기에서 .은 현재 폴더를 의미합니다.


6. 기본 노드 코드 확인
my_first_node.py 파일의 기본 내용은 다음과 비슷합니다.
def main():
print('Hi from my_first_package.')
if __name__ == '__main__':
main()
이 코드는 ROS 2 통신을 아직 사용하지는 않습니다. 단순히 Python 파일이 실행되는지 확인하는 테스트용 코드입니다.
핵심은 아래 부분입니다.
if __name__ == '__main__':
main()
이 조건문은 Python 파일이 직접 실행되었을 때만 main() 함수를 실행하게 합니다.
위에서 if __name__ == '__main__': 는 파이썬에서 거의 필수급으로 쓰는 진입점(entry point) 패턴입니다.
핵심은 딱 하나입니다.
- 이 파일을 "직접 실행했는가?"
- 아니면 다른 파일에서 "import 했는가?"
를 구분하는 코드입니다.
Python 코드가 아래와 같을 경우 예로 들어 설명하겠습니다.
def test():
print("테스트")
if __name__ == '__main__':
test()
실행결과 차이를 먼저 알아보겠습니다.
아래처럼 먼저 직접 실행할 경우
python app.py
이 경우에는
__name__ == '__main__'
가 됩니다. 즉 test()가 실행됩니다.
그리고 다른 파일에서 import 할 경우
import app
일 경우
__name__ == 'app'
이기 때문에 if __name__ == '__main__': 이 거짓이 되어 test()가 실행되지 않습니다.
이것이 왜 중요하나면 위의 조건문이 없으면 import만 해도 프로그램이 실행되기 때문입니다.
따라서 대형 프로젝트 개발 시
- 테스트 코드
- 실행 코드
- main 루프
- 디버그 코드
같은 것은 반드시 if __name__ == '__main__': 아래 넣습니다.
__name__ 은 파이썬이 자동으로 넣어주는 특별 변수입니다.
현재 모듈(파일)의 이름이 들어갑니다.
직접 실행하면 __main__이고 import 하면 파일 이름이 됩니다.
7. 패키지 빌드
코드 입력이 완료되었으면 워크스페이스 루트로 이동 후 빌드합니다.
cd ~/ros2_study
colcon build
정상적으로 빌드되면 다음과 같은 결과가 나옵니다.

7. 바로 실행하면 왜 실패할 수 있는가
빌드 후 바로 아래 명령어를 실행하면 실패할 수 있습니다.
ros2 run my_first_package my_first_node
오류 예시는 다음과 같습니다.
Package 'my_first_package' not found

이것은 패키지가 없는 것이 아니라, 현재 터미널이 새로 빌드된 패키지를 아직 모르는 상태이기 때문입니다.
ROS 2에서는 빌드 후 다음 명령어로 환경을 적용해야 합니다.
source ./install/local_setup.bash

그다음 다시 실행합니다.
ros2 run my_first_package my_first_node
아래의 그림처럼 정상적으로 실행됩니다.

8. 매번 source 하기 귀찮을 때
매번 다음 명령을 입력하는 것은 번거롭습니다.
source ~/ros2_study/install/local_setup.bash
따라서 .bashrc에 아래와 같이 alias를 추가하면 편리합니다.
alias sl='source ~/ros2_study/install/local_setup.bash'

터미널을 다시 실행하여 alias 한 sl 명령을 실행하여 정상적용 여부를 확인합니다.
cd ros2_study/
colcon build
sl
ros2 run my_first_package my_first_node

sl alias가 실행되는지 여부를 확인하고 싶을 때에는 아래와 같이 echo를 사용하여 적절한 문장을 출력할 수 있습니다. .bashrc 파일에 위에서 추가한 sl alias 문을 아래와 같이 수정하여 저장합니다.
alias sl="source ~/ros2_study/install/local_setup.bash; echo \"ros2_study workspace is activated.\""
. bashrc 편집 후 아래와 같이 명령어들을 실행하여 수정한 내용의 적용 여부를 확인합니다.
source .bashrc
cd ros2_study
sl

9. Subscriber 노드 파일 추가
이제 패키지 안에 Subscriber 노드를 추가합니다.
파일 위치는 다음입니다.
~/ros2_study/src/my_first_package/my_first_package/
새 파일 이름은 다음과 같이 만듭니다.
my_subscriber.py
File -> New Text File 메뉴를 선택하면 아래의 그림과 같이 빈 문서가 준비됩니다.

이 파일을 ~/ros2_study/src/my_first_package/my_first_package/ 위치에 my_subscriber.py로 저장합니다.


해당 위치에 파일이 저장되었습니다.
tree 명령어를 실행시켜 다시 확인합니다.

10. Subscriber 기본 코드 예시
아래의 코드를 입력해 주세요.
import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose
class TurtlesimSubscriber(Node):
def __init__(self):
super().__init__('turtlesim_subscriber')
self.subscription = self.create_subscription(
Pose,
'/turtle1/pose',
self.pose_callback,
10
)
def pose_callback(self, msg):
self.get_logger().info(
f'x={msg.x:.2f}, y={msg.y:.2f}, theta={msg.theta:.2f}'
)
def main(args=None):
rclpy.init(args=args)
node = TurtlesimSubscriber()
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
1. def __init__(self):
def __init__(self):
이것은 Python 클래스의 생성자 함수입니다.
객체가 만들어질 때 자동으로 실행됩니다.
예를 들어:
node = TurtlesimSubscriber()
이 코드를 실행하면 Python이 자동으로 아래 함수를 호출합니다.
def __init__(self):
즉, __init__()은 초기 설정을 하는 함수입니다.
ROS 2 노드에서는 보통 여기 안에서 다음 작업을 합니다.
self.publisher = self.create_publisher(...)
self.subscription = self.create_subscription(...)
self.timer = self.create_timer(...)
예:
class TurtlesimSubscriber(Node):
def __init__(self):
super().__init__('turtlesim_subscriber')
self.subscription = self.create_subscription(
Pose,
'/turtle1/pose',
self.pose_callback,
10
)
이런 식으로 노드가 시작될 때 subscriber, publisher, timer 등을 준비합니다.
2. self는 무엇인가요?
def __init__(self):
여기서 self는 현재 객체 자신을 의미합니다.
예를 들어:
node = TurtlesimSubscriber()
이렇게 객체를 만들면, self는 바로 이 node 객체를 가리킵니다.
쉽게 말하면:
self = 지금 만들어지고 있는 이 노드 객체
입니다.
그래서 아래처럼 쓰면:
self.subscription = ...
이 뜻은:
이 노드 객체 안에 subscription이라는 변수를 저장한다
입니다.
3. super().__init__('turtlesim_subscriber')
super().__init__('turtlesim_subscriber')
이 줄이 ROS 2에서 가장 중요합니다.
현재 클래스가 보통 이렇게 되어 있습니다.
class TurtlesimSubscriber(Node):
이 뜻은:
TurtlesimSubscriber 클래스는 Node 클래스를 상속받는다
입니다.
즉, TurtlesimSubscriber는 ROS 2의 Node 기능을 물려받습니다.
Node 안에는 이런 기능들이 들어 있습니다.
create_publisher()
create_subscription()
create_timer()
get_logger()
destroy_node()
그런데 이 기능들을 제대로 쓰려면 부모 클래스인 Node의 초기화가 먼저 필요합니다.
그 역할을 하는 코드가 이것입니다.
super().__init__('turtlesim_subscriber')
4. super()의 의미
super()
는 부모 클래스를 의미합니다.
현재 클래스:
class TurtlesimSubscriber(Node):
에서 부모 클래스는 Node입니다.
그래서:
super().__init__('turtlesim_subscriber')
는 거의 이런 뜻입니다.
Node.__init__(self, 'turtlesim_subscriber')
즉:
ROS 2 Node를 초기화하면서 노드 이름을 turtlesim_subscriber로 설정한다
입니다.
5. 'turtlesim_subscriber'는 무엇인가요?
super().__init__('turtlesim_subscriber')
여기서 문자열 'turtlesim_subscriber'는 ROS 2 노드 이름입니다.
이 노드를 실행하면 ROS 2 시스템 안에서 이 노드는 다음 이름으로 등록됩니다.
/turtlesim_subscriber
확인은 터미널에서 할 수 있습니다.
ros2 node list
출력 예:
/turtlesim
/turtlesim_subscriber
즉, 이 이름은 단순한 Python 클래스 이름이 아니라 ROS 2 네트워크에서 보이는 노드 이름입니다.
6. 왜 꼭 써야 하나요?
이 줄을 안 쓰면:
super().__init__('turtlesim_subscriber')
ROS 2 Node 초기화가 안 됩니다.
그러면 아래 같은 기능을 사용할 때 문제가 생깁니다.
self.create_subscription(...)
self.get_logger().info(...)
self.create_timer(...)
예를 들어 이런 에러가 날 수 있습니다.
AttributeError
또는 노드가 정상적으로 ROS 2 그래프에 등록되지 않습니다.
즉, ROS 2 노드 클래스를 만들 때는 거의 필수입니다.
위의 코드를 VS Code 에디터 창에서 입력하고 Ctrl+S를 입력하여 저장합니다. 저장이 되지 않으면 탭 위에 동그라미가 표시되어 있습니다. 자동 저장은 되지 않습니다.

위의 소스는 turtlesim의 /turtle1/pose 토픽을 구독해서 거북이의 현재 위치와 방향을 출력하는 ROS 2 Python Subscriber 노드입니다.
프로그램의 기능을 요약하면 아래와 같습니다.
turtlesim_node가 /turtle1/pose 토픽으로 위치 정보를 발행
↓
TurtlesumSubscriber 노드가 해당 토픽을 구독
↓
pose_callback() 함수가 메시지를 받을 때마다 실행
↓
x, y, theta 값을 로그로 출력
a. 모듈 import
import rclpy
from rclpy.node import Node
from turtlesim.msg import Pose
rclpy는 ROS 2를 Python에서 사용하기 위한 기본 라이브러리입니다.
Node는 ROS 2 노드를 만들기 위한 기본 클래스입니다. 우리가 만드는 TurtlePoseSubscriber는 이 Node 클래스를 상속받습니다.
Pose는 turtlesim에서 사용하는 위치 메시지 타입입니다. /turtle1/pose 토픽으로 전달되는 데이터 형식입니다.
Pose 메시지에는 주로 다음 값이 들어 있습니다.
x : 거북이의 x 좌표
y : 거북이의 y 좌표
theta : 거북이의 방향 각도
linear_velocity : 선속도
angular_velocity : 각속도
b. Subscriber 클래스 선언
class TurtlesimSubscriber(Node):
TurtlesimSubscriber라는 클래스를 정의합니다.
괄호 안의 Node는 이 클래스가 ROS 2 노드 기능을 상속받는다는 뜻입니다.
즉, 이 클래스는 일반 Python 클래스가 아니라 ROS 2에서 실행 가능한 노드 객체가 됩니다.
c. 생성자 함수
def __init__(self):
super().__init__('turtlesim_subscriber')
__init__()은 클래스 객체가 만들어질 때 자동으로 실행되는 초기화 함수입니다.
super().__init__('turtlesim_subscriber')
이 코드는 부모 클래스인 Node를 초기화합니다.
여기서 지정한 문자열은 노드 이름입니다.
따라서 이 노드는 ROS 2 시스템에서 다음 이름으로 보입니다.
/turtlesim_subscriber
확인 명령어는 다음과 같습니다.
ros2 node list
d. 토픽 구독 생성
self.subscription = self.create_subscription(
Pose,
'/turtle1/pose',
self.pose_callback,
10
)
이 부분이 Subscriber의 핵심입니다.
create_subscription()은 특정 토픽을 구독하는 함수를 생성합니다.
각 인자의 의미는 다음과 같습니다.
Pose : 구독할 메시지 타입
/turtle1/pose : 구독할 토픽 이름
self.pose_callback : 메시지를 받았을 때 실행할 함수
10 : QoS queue depth
즉, 이 코드는 다음 의미입니다.
/turtle1/pose 토픽에서 Pose 타입 메시지가 들어오면
self.pose_callback() 함수를 실행합니다.
10은 메시지를 임시로 저장할 큐 크기입니다. 초급 실습에서는 보통 10을 사용합니다.
e. 콜백 함수
def pose_callback(self, msg):
self.get_logger().info(
f'x={msg.x:.2f}, y={msg.y:.2f}, theta={msg.theta:.2f}'
)
Subscriber는 토픽 데이터를 받을 때마다 콜백 함수를 실행합니다.
여기서는 /turtle1/pose 토픽에서 메시지를 받을 때마다 pose_callback()이 실행됩니다.
msg에는 turtlesim의 위치 정보가 들어 있습니다.
msg.x
msg.y
msg.theta
각 값의 의미는 다음과 같습니다.
msg.x : 거북이의 x 좌표
msg.y : 거북이의 y 좌표
msg.theta : 거북이의 방향
출력 부분은 다음과 같습니다.
self.get_logger().info(...)
이는 ROS 2 방식의 로그 출력입니다.
책 예제처럼 print()를 사용할 수도 있지만, ROS 2에서는 보통 get_logger().info()를 사용하는 것이 더 좋습니다.
출력 예시는 다음과 같습니다.
[INFO] [turtlesim_subscriber]: x=5.54, y=5.54, theta=0.00
:.2f는 소수점 둘째 자리까지만 출력하겠다는 뜻입니다.
예를 들어 원래 값이 다음과 같다면,
5.54444561004639
출력은 다음처럼 정리됩니다. 즉 소수점 자릿수를 2로 정의한 것입니다.
5.54
f. main 함수
def main(args=None):
rclpy.init(args=args)
main() 함수는 이 노드가 실행될 때 시작 지점 역할을 합니다.
rclpy.init(args=args)
이 코드는 ROS 2 Python 시스템을 초기화합니다. ROS 2 노드를 만들기 전에 반드시 실행해야 합니다.
g. 노드 객체 생성
node = TurtlePoseSubscriber()
앞에서 만든 TurtlePoseSubscriber 클래스로 실제 노드 객체를 생성합니다.
이 순간 다음 작업이 수행됩니다.
1. Node 초기화
2. 노드 이름 등록
3. /turtle1/pose 토픽 구독 설정
h. spin으로 노드 실행 유지
try:
rclpy.spin(node)
except KeyboardInterrupt:
pass
rclpy.spin(node)는 노드를 계속 실행 상태로 유지합니다.
Subscriber는 데이터를 기다려야 하므로, 프로그램이 바로 종료되면 안 됩니다.
spin()이 실행되는 동안 노드는 계속 살아 있고, /turtle1/pose 메시지가 들어올 때마다 pose_callback()이 호출됩니다.
try-except KeyboardInterrupt는 사용자가 Ctrl + C로 종료했을 때 에러 메시지를 줄이기 위한 구조입니다.
즉, 터미널에서 다음 키를 눌러도 깔끔하게 종료됩니다.
Ctrl + C
i. 노드 종료 처리
node.destroy_node()
rclpy.shutdown()
destroy_node()는 생성한 노드를 정리합니다.
rclpy.shutdown()은 ROS 2 Python 시스템을 종료합니다.
프로그램 종료 시 자원을 정리하는 부분입니다.
j. 직접 실행 조건문
if __name__ == '__main__':
main()
이 코드는 Python 파일이 직접 실행될 때 main() 함수를 호출합니다.
예를 들어 다음처럼 실행하면 동작합니다.
python3 my_subscriber.py
ROS 2 패키지에서는 보통 setup.py에 등록한 뒤 다음처럼 실행합니다.
ros2 run my_first_package my_subscriber
k. 도움 되는 말
이 예제는 단순히 turtlesim 좌표를 출력하는 코드이지만, 실제 로봇에서는 같은 구조가 센서 데이터 수신에 사용됩니다.
예를 들어 다음과 같이 확장됩니다.
/turtle1/pose → turtlesim 위치 정보
/odom → 모바일 로봇 위치 추정 정보
/imu/data → IMU 센서 정보
/scan → LiDAR 거리 정보
/camera/image_raw → 카메라 이미지 정보
즉, Subscriber 노드는 로봇에서 “외부 데이터를 받아 처리하는 기본 구조”입니다.
실제 로봇 시스템에서는 이 콜백 함수 안에서 다음과 같은 일을 하게 됩니다.
1. 위치 정보 저장
2. 센서 데이터 필터링
3. 장애물 감지
4. 목표 지점 도달 여부 판단
5. 제어 명령 계산
그래서 ROS 2를 공부할 때 Subscriber 구조는 반드시 익혀야 하는 핵심입니다.
11. setup.py에 실행 등록하기
새 노드를 만들었다고 해서 바로 ros2 run으로 실행되지는 않습니다. setup.py에 실행 명령을 등록해야 합니다.
setup.py에서 entry_points 부분을 찾습니다.
entry_points={
'console_scripts': [
'my_first_node = my_first_package.my_first_node:main',
],
},
아래처럼 my_subscriber를 추가합니다.
entry_points={
'console_scripts': [
'my_first_node = my_first_package.my_first_node:main',
'my_subscriber = my_first_package.my_subscriber:main',
],
},
주의할 점은 쉼표입니다. 여러 실행 항목을 넣을 때는 각 줄 끝에 쉼표가 있어야 합니다.

이 설정을 추가하면 다음 명령으로 실행할 수 있습니다.
ros2 run my_first_package my_subscriber
12. 다시 빌드하고 환경 적용하기
파일을 추가했거나 setup.py를 수정했다면 다시 빌드해야 합니다.
cd ~/ros2_study
colcon build
sl
여기서 sl 명령어는 .bashrc에 등록한 " source ./install/local_setup.bash" 실행을 위한 alias 입니다. 이 명령어는 현재 터미널에 새 환경을 다시 적용합니다.

13. turtlesim 실행 후 테스트
터미널 1:
ros2 run turtlesim turtlesim_node

터미널 2:
ros2 run my_first_package my_subscriber
거북이를 움직이면 Subscriber 노드에서 위치 값이 출력됩니다.

rqt_graph를 실행하여 노드와 토픽의 흐름도를 확인합니다.

정상 구조는 다음과 같습니다.
/turtlesim → /turtle1/pose → /turtlesim_subscriber
이 구조는 다음 의미입니다.
/turtlesim 노드가 /turtle1/pose 토픽을 발행합니다.
/turtlesim_subscriber 노드가 /turtle1/pose 토픽을 구독합니다.
즉, Subscriber 노드가 turtlesim의 위치 정보를 정상적으로 받고 있다는 뜻입니다.
종료하려면 Ctrl+c를 입력합니다.

14. 자주 발생하는 문제
a. Package not found
원인:
source ./install/local_setup.bash를 하지 않음
해결:
cd ~/ros2_study
source ./install/local_setup.bash
또는
sl
b. Executable not found
원인:
setup.py의 console_scripts에 실행 파일을 등록하지 않음
해결:
'my_subscriber = my_first_package.my_subscriber:main',
추가 후 다시 빌드합니다.
colcon build
source ./install/local_setup.bash
또는
sl
c. import rclpy 에러
원인:
ROS 2 환경이 활성화되지 않음
해결:
source /opt/ros/humble/setup.bash
15. Publisher 노드 만들기
이제 거북이를 직접 움직이는 Publisher 노드를 추가합니다.
VS Code에서 File -> New Text File을 선택하고 파일을 생성합니다.
아래의 코드를 입력한 다음 my_publisher.py로 ~/ros2_study/src/my_first_package/my_first_package 에 파일을 저장합니다.
import rclpy as rp
from rclpy.node import Node
from geometry_msgs.msg import Twist
class TurtlesimPublisher(Node):
def __init__(self):
super().__init__('turtlesim_publisher')
self.publisher = self.create_publisher(
Twist,
'/turtle1/cmd_vel',
10
)
timer_period = 0.5
self.timer = self.create_timer(
timer_period,
self.timer_callback
)
def timer_callback(self):
msg = Twist()
msg.linear.x = 2.0
msg.angular.z = 2.0
self.publisher.publish(msg)
def main(args=None):
rp.init(args=args)
turtlesim_publisher = TurtlesimPublisher()
rp.spin(turtlesim_publisher)
turtlesim_publisher.destroy_node()
rp.shutdown()
if __name__ == '__main__':
main()



16. Publisher 코드 구조 설명
a. Twist 메시지
from geometry_msgs.msg import Twist
Twist는 속도 명령 메시지입니다. turtlesim에서는 /turtle1/cmd_vel 토픽으로 Twist 메시지를 보내면 거북이가 움직입니다.
Twist의 주요 필드는 다음과 같습니다.
linear.x : 전진/후진 속도
angular.z : 회전 속도
b. Publisher 생성
self.publisher = self.create_publisher(
Twist,
'/turtle1/cmd_vel',
10
)
이 코드는 /turtle1/cmd_vel 토픽으로 Twist 메시지를 발행하는 Publisher를 만듭니다.
각 인자의 의미는 다음과 같습니다.
Twist : 발행할 메시지 타입
/turtle1/cmd_vel : 발행할 토픽 이름
10 : QoS queue depth
c. Timer 생성
timer_period = 0.5
self.timer = self.create_timer(
timer_period,
self.timer_callback
)
이 코드는 0.5초마다 timer_callback() 함수를 실행하게 만듭니다.
즉, 0.5초마다 속도 명령을 계속 발행합니다.
d. 속도 명령 발행
def timer_callback(self):
msg = Twist()
msg.linear.x = 2.0
msg.angular.z = 2.0
self.publisher.publish(msg)
이 코드는 거북이에게 다음 명령을 보냅니다.
앞으로 2.0 속도로 이동
z축 기준으로 2.0 속도로 회전
그래서 turtlesim 화면에서는 거북이가 원을 그리며 움직입니다.
17. setup.py에 Publisher 등록하기
setup.py의 console_scripts에 Publisher도 추가합니다.
entry_points={
'console_scripts': [
'my_first_node = my_first_package.my_first_node:main',
'my_subscriber = my_first_package.my_subscriber:main',
'my_publisher = my_first_package.my_publisher:main',
],
},

이제 다음 명령으로 Publisher를 실행할 수 있습니다.
ros2 run my_first_package my_publisher
setup.py를 수정했으므로 다시 빌드해야 합니다.
cd ~/ros2_study
colcon build
source install/local_setup.bash
또는
sl

18. 빌드 정보 초기화가 필요한 경우
가끔 파일을 수정했는데 실행 결과가 이상하거나 예전 상태가 유지되는 것처럼 보일 때가 있습니다.
이럴 때는 빌드 결과를 지우고 다시 빌드할 수 있습니다.
cd ~/ros2_study
sudo rm -r build install log
삭제 후 확인합니다.
ls
정상이라면 다음처럼 src만 남습니다.
src
다시 빌드합니다.
colcon build
source install/local_setup.bash
또는
sl
단, 습관적으로 매번 지울 필요는 없습니다. 일반적인 수정은 colcon build만 다시 해도 충분합니다.
19. turtlesim, Publisher, Subscriber 함께 실행하기
터미널을 3개 사용합니다.
a. 터미널 1: turtlesim 실행
ros2 run turtlesim turtlesim_node

b. 터미널 2: Publisher 실행
ros2 run my_first_package my_publisher

c. 터미널 3: Subscriber 실행
ros2 run my_first_package my_subscriber

정상적으로 실행되면 다음 현상이 나타납니다.
1. turtlesim 창에서 거북이가 원을 그리며 움직입니다.
2. Publisher는 /turtle1/cmd_vel 토픽으로 속도 명령을 보냅니다.
3. Subscriber는 /turtle1/pose 토픽으로 위치 값을 받아 출력합니다.
d. 노드 목록 확인:
ros2 node list

e. 토픽 목록 확인:
ros2 topic list

f. 토픽 타입 확인:
ros2 topic info /turtle1/cmd_vel

g. 직접 토픽 출력 확인:
ros2 topic echo /turtle1/pose

20. turtlesim이 움직이지 않는 경우
확인할 것:
1. turtlesim_node가 실행 중인지 확인합니다.
2. my_publisher가 실행 중인지 확인합니다.
3. /turtle1/cmd_vel 토픽이 존재하는지 확인합니다.
4. setup.py 수정 후 다시 빌드했는지 확인합니다.
확인 명령:
ros2 topic list
ros2 node list
rqt_graph
21. 지금까지 정리
이번 실습에서는 ROS 2 Python 패키지 안에 Subscriber와 Publisher 노드를 직접 추가했습니다. Subscriber는 /turtle1/pose 토픽을 구독하여 turtlesim의 위치를 출력했고, Publisher는 /turtle1/cmd_vel 토픽으로 속도 명령을 발행하여 거북이를 움직였습니다.
핵심은 다음 3가지입니다.
create_subscription() : 토픽을 구독합니다.
create_publisher() : 토픽을 발행합니다.
create_timer() : 일정 주기로 함수를 실행합니다.
ROS 2의 토픽 통신은 로봇 시스템에서 센서 데이터, 제어 명령, 상태 정보 전달에 가장 많이 사용됩니다. 이번 예제는 단순한 turtlesim 실습이지만, 실제 로봇에서는 다음과 같이 확장됩니다.
카메라 노드 → 이미지 토픽 발행
LiDAR 노드 → 거리 데이터 토픽 발행
제어 노드 → 속도 명령 토픽 발행
상태 모니터 노드 → 위치, 배터리, 센서 상태 구독
즉, 이번에 만든 Publisher와 Subscriber 구조는 ROS 2 로봇 프로그램의 기본 뼈대라고 보시면 됩니다.
'강좌 > ROS2' 카테고리의 다른 글
| ROS2 사용자 정의 메세지 만들기 #2 (0) | 2026.05.09 |
|---|---|
| ROS2 사용자 정의 메세지 만들기 #1 (0) | 2026.05.09 |
| ROS 2 Python Service Client 사용하기 (0) | 2026.05.08 |
| Python에서 ROS2 토픽 발행 (0) | 2026.05.07 |
| Python에서 ROS2 토픽 구독 (0) | 2026.05.07 |
