workspace & package

목표 구조

workspace/
├── build/
│   └── ...
├── install/
│   ├── local_setup.bash
│   ├── setup.bash
│   └── ...
├── log/
│   └── ...
└── src/
    ├── my_pkg/
    │   ├── my_pkg/
    │   │   ├── __init__.py
    │   │   ├── publisher_node.py
    │   │   └── subscriber_node.py
    │   ├── resource/
    │   │   └── my_pkg
    │   ├── package.xml
    │   ├── setup.cfg
    │   ├── setup.py
    │   └── test/
    │       └── ...
    └── my_pkg_msgs/
        ├── msg/
        │   └── MyMessage.msg
        ├── CMakeLists.txt
        └── package.xml
  • my_pkg/my_pkg/: Python 패키지 코드와 노드(publisher_node, subscriber_node).
  • my_pkg_msgs/msg/: 커스텀 메시지 정의(.msg).

1. 워크스페이스 폴더 만들기

워크스페이스 루트 폴더 이름을 **workspace**로 하고, 그 안에 소스용 src 폴더를 만든다.

mkdir -p workspace/src
cd workspace

2. colcon build로 install, build, log 생성

workspace 폴더에서 colcon build를 실행하면 install, build, log 폴더가 생성된다. (처음에는 src가 비어 있어도 빌드할 패키지가 없어 곧 끝난다.)

cd workspace
colcon build

이후 workspace 아래에 install/, build/, log/ 디렉터리가 생긴다.

3. src 안에서 ROS 2 패키지 만들기 (ament_python)

src로 들어가서 ros2 pkg createPython 패키지를 만든다. 빌드 타입은 ament_python으로 한다.

cd workspace/src
ros2 pkg create my_pkg --build-type ament_python

의존성이 필요하면 --dependencies로 지정한다.

ros2 pkg create my_pkg --build-type ament_python --dependencies rclpy std_msgs

C++ 등 다른 빌드 타입이 필요하면 --build-type ament_cmake를 사용하면 된다.

4. 다시 colcon build 후 환경 로드

패키지를 추가했으면 워크스페이스 루트에서 다시 빌드하고, 사용 전에 install을 source 한다.

cd workspace
colcon build
source install/setup.bash   # Linux/macOS
# Windows: install\setup.bat

setup.bash vs local_setup.bash

  • setup.bash: 이 워크스페이스 + 언더레이(이 워크스페이스를 빌드할 때 기반으로 한 워크스페이스, 예: 시스템 ROS 2) 까지 모두 source 한다. 보통 이 워크스페이스에서 작업할 때 한 번만 쓰면 된다.
  • local_setup.bash: 이 워크스페이스만 source 한다. 언더레이는 건드리지 않는다. 워크스페이스를 겹쳐 쓸 때(예: A 위에 B를 올려 빌드한 뒤, B의 setup을 쓸 때 A는 이미 로드된 상태) 아래쪽 워크스페이스는 local_setup만 쓰는 식으로 구분할 수 있다.

5. 메시지 패키지 만들기 ([package_name]_msgs)

메시지 타입을 정의하려면 별도 패키지를 만든다. 관례상 이름은 <패키지명>_msgs (예: my_pkg_msgs)로 둔다.

msg 패키지를 왜 ament_cmake로 만드나?
.msg·.srv·.action 파일은 rosidl(ROS Interface Definition Language)로 처리된다. rosidl이 C++/Python 등 여러 언어용 코드를 생성하는데, 이 코드 생성·설치가 CMake 쪽에 통합되어 있다. rosidl_generate_interfaces() 같은 함수는 ament_cmake에서 제공하므로, 인터페이스(메시지) 전용 패키지는 ament_cmake로 두는 것이 ROS 2의 표준 방식이다. ament_python에는 .msg 정의부터 빌드까지 넣는 공식 흐름이 없고, 메시지 패키지는 한 번 ament_cmake로 빌드해 두면 생성된 Python 바인딩을 ament_python 노드에서 from my_pkg_msgs.msg import MyMessage처럼 그대로 쓸 수 있다.

cd workspace/src
ros2 pkg create my_pkg_msgs --build-type ament_cmake

msg 디렉터리와 메시지 파일을 만든다.

mkdir -p my_pkg_msgs/msg

my_pkg_msgs/msg/MyMessage.msg 예시:

string name
int32 count
float32 value

package.xml에 다음 의존성을 추가한다.

<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

package.xml은 ROS 2 패키지의 메타데이터를 담는 파일이다. 패키지 이름·버전·설명, 빌드 시 필요한 의존성(build_depend), 실행 시 필요한 의존성(exec_depend), 빌드 도구·export 정보 등을 정의한다. colcon이 이 파일을 읽어 의존 순서를 정하고, 다른 패키지가 이 패키지를 찾을 때도 사용한다.

CMakeLists.txt에서 기존 내용을 정리하고, 다음을 넣는다.

find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/MyMessage.msg"
)
ament_export_dependencies(rosidl_default_runtime)

이후 워크스페이스에서 colcon build하면 my_pkg_msgs에서 my_pkg_msgs/msg/MyMessage 메시지 타입을 쓸 수 있다. 사용하는 패키지(예: my_pkg)의 package.xml<depend>my_pkg_msgs</depend>**, **setup.py**의 install_requires'my_pkg_msgs'를 넣고, 노드에서 from my_pkg_msgs.msg import MyMessage`로 가져온다.

6. 퍼블리셔·서브스크라이버 노드 예제 (토픽 발행)

my_pkg토픽을 발행하는 노드구독하는 노드를 추가한다. 여기서는 표준 메시지 std_msgs/String을 사용한다.

퍼블리셔 노드 my_pkg/my_pkg/publisher_node.py:

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


class PublisherNode(Node):
    def __init__(self):
        super().__init__('publisher_node')
        self.publisher_ = self.create_publisher(String, 'chatter', 10)
        self.timer = self.create_timer(1.0, self.timer_callback)
        self.count = 0

    def timer_callback(self):
        msg = String()
        msg.data = f'Hello, ROS 2! ({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 = PublisherNode()
    try:
        rclpy.spin(node)
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

서브스크라이버 노드 my_pkg/my_pkg/subscriber_node.py:

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


class SubscriberNode(Node):
    def __init__(self):
        super().__init__('subscriber_node')
        self.subscription = self.create_subscription(
            String, 'chatter', self.callback, 10)

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


def main(args=None):
    rclpy.init(args=args)
    node = SubscriberNode()
    try:
        rclpy.spin(node)
    finally:
        node.destroy_node()
        rclpy.shutdown()


if __name__ == '__main__':
    main()

setup.pyentry_points에 노드를 등록한다.

entry_points={
    'console_scripts': [
        'publisher_node = my_pkg.publisher_node:main',
        'subscriber_node = my_pkg.subscriber_node:main',
    ],
},

빌드 후 두 터미널에서 각각 실행한다.

source install/setup.bash
ros2 run my_pkg publisher_node   # 터미널 1
ros2 run my_pkg subscriber_node  # 터미널 2

퍼블리셔가 chatter 토픽에 1초마다 메시지를 보내고, 서브스크라이버가 그 메시지를 출력한다. 커스텀 메시지를 쓰려면 먼저 my_pkg_msgs를 빌드하고, 위 노드에서 std_msgs.msg.String 대신 my_pkg_msgs.msg.MyMessage를 사용하면 된다.

예: MyMessage 사용 (퍼블리셔)

from my_pkg_msgs.msg import MyMessage

# 퍼블리셔 생성
self.publisher_ = self.create_publisher(MyMessage, 'chatter', 10)

# 타이머 콜백에서 발행
def timer_callback(self):
    msg = MyMessage()
    msg.name = 'robot'
    msg.count = self.count
    msg.value = 3.14
    self.publisher_.publish(msg)
    self.count += 1

예: MyMessage 사용 (서브스크라이버)

from my_pkg_msgs.msg import MyMessage

self.subscription = self.create_subscription(
    MyMessage, 'chatter', self.callback, 10)

def callback(self, msg):
    self.get_logger().info(f'Received: name={msg.name}, count={msg.count}, value={msg.value}')

요약

단계명령/위치
목표 구조문서 상단 목표 구조 (최종 폴더 모습) 참고
워크스페이스mkdir -p workspace/srccd workspace
install/build/log 생성colcon build (workspace에서)
패키지 생성cd srcros2 pkg create my_pkg --build-type ament_python
메시지 패키지ros2 pkg create my_pkg_msgs --build-type ament_cmakemsg/.msg 정의, package.xml·CMakeLists 수정
퍼블리셔/서브스크라이버my_pkg 안에 노드 스크립트 추가 → setup.py entry_points 등록
빌드·환경 로드colcon buildsource install/setup.bash