본문 바로가기

LAB

[ROS2] 노드 간 양방향 통신 시뮬레이션 구현

해당 글에서는 ubuntu 22.04 LTS 환경에서 ROS2 Humble 버전을 사용하여 개발을 진행한다.


ROS2(Robot Operating System 2)는 이름에서 알 수 있듯이 로봇 개발을 위해 사용하는 프레임워크이다.

구성 요소
Node: 독립적으로 실행되는 모듈 (ex. 센서, ui 등)
Topic: 노드 간 전달하는 데이터가 이동하는 통로
Message: 데이터를 교환하는 형식

프로젝트 구조
ROS2는 workspace라는 폴더 구조로 관리된다.

workspace/
 ├── src/
 ├── install/
 ├── build/
 └── log/

src/: 소스 코드 (패키지 저장소)
install/: 빌드 결과물
build/: 임시 빌드 파일
log/: 실행 로그

ROS2는 토픽 이름만 동일하면 서로 다른 언어(c++, python) 간에도 자동으로 통신이 가능하다! 아주 매력적인 프레임워크!

그럼 이제 시작해 보자 ~.~


우선 우분투 환경에서 진행하여야 함을 잊지 말자.
윈도우 유저일 경우, 멀티 부팅 또는 버추얼박스 등의 가상 머신(VM)을 통해 접속할 수 있다.

위 방법은 다음 글에서 소개하도록 하겠습니당... ^ㅁ^


터미널을 켜서 아래 명령어를 입력한다. 시스템 업데이트를 하는 명령어이다.

apt install과 세트처럼 붙어다니는 명령어라 차츰 익숙해질 것이다 ㅎ.ㅎ

sudo apt update && sudo apt upgrade -y


ROS2을 설치하기 위해 레포지토리를 추가한다.

sudo apt install software-properties-common -y
sudo add-apt-repository universe
sudo apt update && sudo apt install curl -y
sudo curl -sSL https://raw.githubusercontent.com/ros/rosdistro/master/ros.key \
    -o /usr/share/keyrings/ros-archive-keyring.gpg
echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/ros-archive-keyring.gpg] \
    http://packages.ros.org/ros2/ubuntu $(. /etc/os-release && echo $UBUNTU_CODENAME) main" \
    | sudo tee /etc/apt/sources.list.d/ros2.list > /dev/null


그리고 ROS2 설치를 진행한다.

sudo apt update
sudo apt install ros-humble-desktop -y


!ROS2에서 워크스페이스를 사용할 때는 반드시 환경을 불러와야 한다!
편의를 위해 터미널을 열 때마다 자동으로 ROS2 환경에 적용되도록 해 주는 명령어를 사용하자.

echo "source /opt/ros/humble/setup.bash" >> ~/.bashrc
source ~/.bashrc


설치가 완료되었는지 확인해 보자.

ros2 --version

정상적으로 설치가 되었으면 ros2의 버전이 출력된다.


이제 워크스페이스를 생성하여 빌드해 보자.
mkdir은 make directory를 줄인 명령어로, 새로운 디렉토리를 생성할 때 사용한다.

mkdir -p ~/ws/src
cd ~/ws


그리고 빌드 수행 후 구조를 확인한다.
빌드란, ROS2 패키지를 컴퓨터가 이해하고 실행할 수 있는 상태로 만드는 과정이라고 생각하면 된다.

colcon은 ROS2의 공식 빌드 도구이다.

colcon build


빌드 후 구조를 확인해 보자.

tree -L 2


트리 모양으로 폴더 디렉토리가 출력되면 성공이다.
ex.

ws
├── build
├── install
└── src


!다시 강조하지만 ROS2에서 워크스페이스를 사용할 때는 반드시 환경을 불러와야 한다!
마찬가지로 편의를 위해 터미널을 열 때마다 자동으로 ROS2 환경에 적용되도록 해 주자.

echo "source ~/ws/install/setup.bash" >> ~/.bashrc
source ~/.bashrc

이제 ROS2가 제대로 동작하는지 확인해 보자.
노드 간 핑퐁(publisher-subscriber)을 실행해 볼 것이다.

터미널을 두 개 켜서 실행한다.
하나는 publisher, 하나는 subsciber로 하여 핑퐁이
되는지 확인할 것이다.

Publisher: 특정 토픽으로 메시지를 발행하는 노드 (= 데이터를 외부로 전송하는 노드)
Subcriber: 특정 노드를 구독하여 다른 노드가 발행한 메시지를 받아 처리하는 노드 (= 퍼블리셔가 보낸 메시지를 수신하는 노드)

(Publisher) 첫 번째 터미널에 아래 명령어를 입력한다.

ros2 run demo_nodes_cpp talker


(Subcriber) 두 번째 터미널에 아래 명령어를 입력한다.

ros2 run demo_nodes_cpp listener


아래와 같이 실행 결과가 뜨면 성공이다.

[INFO] [talker]: Publishing: 'Hello World: 1'
[INFO] [listener]: I heard: [Hello World: 1]

이제 퍼블리셔와 서브스크라이버 각각의 파일을 생성하여 핑퐁을 시도해 보자.

우선 퍼블리셔를 생성하기 위해 새 패키지를 생성한다.

cd ~/ws/src
ros2 pkg create --build-type ament_python my_publisher_pkg


아래와 비슷한 결과가 출력되면 정상적으로 생성된 것이다.

creating folder ./my_publisher_pkg
creating ./my_publisher_pkg/package.xml
creating ./my_publisher_pkg/setup.py
creating ./my_publisher_pkg/my_publisher_pkg/__init__.py


생성이 되었으면 해당 폴더에 노드 파일을 생성해 보자.

cd ~/ros2_ws/src/my_publisher_pkg/my_publisher_pkg
nano simple_publisher.py


그리고 아래 코드를 입력한다.

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class SimplePublisher(Node):

    def __init__(self):
        super().__init__('simple_publisher')
        self.publisher_ = self.create_publisher(String, 'greetings', 10)
        timer_period = 1.0  # 1초마다 메시지 발행
        self.timer = self.create_timer(timer_period, self.timer_callback)
        self.count = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello ROS2 World! Count: {self.count}'
        self.publisher_.publish(msg)
        self.get_logger().info(f'Publishing: "{msg.data}"')
        self.count += 1

def main(args=None):
    rclpy.init(args=args)
    node = SimplePublisher()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

init 함수에서는 부모 클래스(node)의 생성자를 simple_publisher라는 이름으로 호출하고, 퍼블리셔 객체를 생성한다. 메시지 타입은 string, 토픽 이름은 greetings, 큐 사이즈는 10으로 지정하였다. 그리고 타이머 객체를 생성하여 1초마다 콜백 함수가 호출되도록 한다. 카운터 변수를 통해 발행될 때마다 메시지에 순번을 매긴다.

timer 함수는 앞서 말했듯 1초마다 자동으로 실행된다. 메시지 객체를 생성하고, 해당 객체에 전송할 문자열 데이터와 카운트 숫자를 저장한다. 그리고 퍼블리셔를 통해 메시지를 발행하고, 콘솔(로그)에 현재 발행 중인 메시지를 출력한다.

main 함수에서는 우선 init를 불러와 시스템을 초기화(필수)하고, simple_publisher 클래스(= 노드)를 실제로 인스턴스화한다. 노드가 종료될 때까지 계속 콜백하여 1초마다 계속 메시지가 출력되도록 한다. 그리고 노드가 종료되면 자원을 정리하고, shutdown을 통해 시스템을 완전히 종료한다.


이제 루트 경로로 돌아가서 빌드를 수행하자.

cd ~/ws
colcon build
source install/setup.bash


그리고 퍼블리셔를 실행한다.

ros2 run my_publisher_pkg simple_publisher


아래와 같이 출력되면 성공이다!

[INFO] [simple_publisher]: Publishing: "Hello ROS2 World! Count: 0"
[INFO] [simple_publisher]: Publishing: "Hello ROS2 World! Count: 1"
[INFO] [simple_publisher]: Publishing: "Hello ROS2 World! Count: 2"
...

이제 서브스크라이버를 생성해 보자. 마찬가지로 새 패키지를 생성한다.

cd ~/ws/src
ros2 pkg create --build-type ament_python my_subscriber_pkg


그리고 패키지 폴더 안에 노드 파일을 생성한다.

cd ~/ws/src/my_subscriber_pkg/my_subscriber_pkg
nano simple_subscriber.py


파일 안에 다음 코드를 붙여넣자.

import rclpy
from rclpy.node import Node
from std_msgs.msg import String

class SimpleSubscriber(Node):

    def __init__(self):
        super().__init__('simple_subscriber')
        self.subscription = self.create_subscription(
            String,           # 메시지 타입
            'greetings',      # 토픽 이름 (퍼블리셔와 동일해야 함)
            self.listener_callback,  # 콜백 함수
            10                # 큐 사이즈
        )
        self.subscription  # 방지용 (linter warning)

    def listener_callback(self, msg):
        self.get_logger().info(f'Received message: "{msg.data}"')

def main(args=None):
    rclpy.init(args=args)
    node = SimpleSubscriber()
    rclpy.spin(node)
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

create_subscription 함수에서 서브스크라이버를 생성하고 메시지 형식을 string을 지정한다.
listener_callback 함수는 메시지를 받을 때마다 실행된다.

그리고 setup.py에 실행 엔트리를 추가한다.

entry_points={
    'console_scripts': [
        'simple_subscriber = my_subscriber_pkg.simple_subscriber:main',
    ],
},


이제 워크스페이스 루트로 이동하여 빌드한다.

cd ~/ws
colcon build
source install/setup.bash


거의 다 왔다!!! 🥹
마지막으로 퍼블리셔와 서브스크라이버를 동시에 실행해 보자. 터미널 두 개를 열어 다음 명령어를 각각 입력한다.

ros2 run my_publisher_pkg simple_publisher
ros2 run my_subscriber_pkg simple_subscriber


퍼블리셔 실행 결과

[INFO] [simple_publisher]: Publishing: "Hello ROS2 World! Count: 0"
[INFO] [simple_publisher]: Publishing: "Hello ROS2 World! Count: 1"

서브스크라이버 실행 결과

[INFO] [simple_subscriber]: Received message: "Hello ROS2 World! Count: 0"
[INFO] [simple_subscriber]: Received message: "Hello ROS2 World! Count: 1"

개인적으로 넘 어려웠으면서도 좋아했던 부분이라 호흡이 좀 길게 작성된 것 같기도 하구...
이해되지 않는 부분이 있거나, 오류 혹은 제대로 실행되지 않는 구간이 있다면 알려 주세요!

참고한 글
https://wikidocs.net/book/18466

📕 ROS2 로봇 프로그래밍 완성 가이드

### Arduino부터 자율주행까지 **지은이 : Dinner Coffee** --- ## 📘 책 소개 『ROS2 로봇 프로그래밍 완성 가이드 — Ardu…

wikidocs.net

'LAB' 카테고리의 다른 글

연구 디벨롭 사항 - KSC  (0) 2025.11.19
영상 데이터셋 .arrow train (실패 💀)  (0) 2025.11.05
[ML] model train - 모델 학습시키는 방법 (기초)  (0) 2025.11.01
연구 내용 정리  (0) 2025.10.29
[Neo4j] .dump file 불러오기  (1) 2025.04.09