AEGLOSLAB ROS 2 Jazzy 대전

제1부 계산 그래프의 코어

1.2 인터페이스 정의 — msg·srv·action과 IDL

1.2.1 이 장에서 배우는 것 / 선수 지식

인터페이스는 시스템에서 가장 오래 사는 것이다. 노드는 다시 쓰고 알고리즘은 갈아치우지만, 메시지 정의는 한번 배포되면 바꾸기 어렵다 — 바꾸는 순간 그 타입을 쓰는 모든 노드·모든 bag 파일·모든 외부 연동이 함께 흔들린다. 그래서 인터페이스 설계는 API 설계이자 데이터 스키마 설계이며, 코드보다 신중하게 다뤄야 한다.

배우는 것:

  • .msg·.srv·.action 문법의 전수 — 타입, 배열(고정·유한·무한), 기본값, 상수, 중첩.
  • 문자열·배열의 한계 지정이 왜 성능·안전에 직결되는가.
  • IDL로의 변환 — ROS 2 인터페이스가 실제로는 OMG IDL 위에 구축돼 있다는 사실과 그 함의.
  • 인터페이스 패키지의 빌드 구조 — rosidl_generate_interfaces, 의존 선언, 순환 의존을 피하는 배치.
  • 표준 인터페이스 카탈로그 — 무엇이 이미 있고, 언제 새로 만들어야 하는가.
  • 인터페이스 진화 전략 — 호환을 깨지 않고 바꾸는 방법과, 깨야 할 때의 절차.
  • 설계 판단 — 큰 메시지 하나 vs 작은 메시지 여럿, 헤더 포함 여부, 커스텀 타입의 비용.

선수 지식: 0.6(패키지 구조·빌드), 1.1(이름). 직렬화·타입 지원의 내부는 다음 장 1.3에서 다루므로 여기서는 "무엇을 어떻게 쓰는가"에 집중한다.

정직: 아래 문법·타입 표는 rosidl_adapter의 파서와 ROS 2 인터페이스 설계 문서를 근거로 정리했다. 이 환경에서 빌드해 검증하지 않았다(목차 3) — 다만 액션이 서비스 3개·토픽 2개로 분해되는 것은 실물에서 실행 확인했다(1.2.6). 특히 유한 배열·유한 문자열의 실제 적용 범위는 RMW 구현에 따라 다를 수 있어 해당 절에 (확인 필요)로 표시했다.

1.2.2 세 가지 인터페이스와 통신 패턴의 대응

파일통신 패턴구조방향성
.msg토픽(발행·구독)필드 목록단방향·다대다·비동기
.srv서비스(요청·응답)요청 --- 응답양방향·1:1·단발
.action액션(목표·피드백·결과)목표 --- 결과 --- 피드백양방향·장시간·취소 가능

패턴 선택 자체는 1.4(토픽)·1.7(서비스)·1.8(액션)에서 판단 기준과 함께 다룬다. 여기서는 정의 파일을 어떻게 쓰는가가 주제다.

인터페이스 패키지의 표준 배치는 다음과 같다.

경로내용비고
my_interfaces/msg/MotorState.msg토픽 메시지파일명 UpperCamelCase
my_interfaces/srv/SetMode.srv서비스동일
my_interfaces/action/Calibrate.action액션동일
my_interfaces/package.xml메타데이터·의존member_of_group 필수(1.2.5)
my_interfaces/CMakeLists.txtrosidl_generate_interfaces 호출동일

msg·srv·action 디렉터리 이름은 관례가 아니라 도구가 기대하는 이름이다(ros2 interface 계열과 생성기가 이 배치를 전제한다).

파일명은 UpperCamelCase여야 한다(MotorState.msg, motor_state.msg 아님). 생성되는 타입 이름이 파일명에서 나오기 때문이다. 필드 이름은 snake_case.

1.2.3 기본 타입과 필드 문법

기본 타입:

분류타입
불리언bool
정수int8 uint8 int16 uint16 int32 uint32 int64 uint64
실수float32 float64
문자char(= uint8), byte(= uint8) — 의미만 다름
문자열string, wstring

ROS 1의 time·duration 내장 타입은 없다. ROS 2에서는 builtin_interfaces/msg/Time·builtin_interfaces/msg/Duration 메시지 타입을 쓴다. 이것은 단순한 이름 변경이 아니라 설계 변경이다 — 시간이 일반 메시지가 되면서 IDL로 표현 가능해졌다(1.2.6).

필드 선언:

# MotorState.msg
std_msgs/Header header          # 다른 패키지 타입 중첩
string name                     # 무한 문자열
float64 position                # 라디안 또는 미터 (REP 103)
float64 velocity
float64 effort
uint8 mode 0                    # 기본값 0
bool enabled true               # 기본값 true

배열 세 종류 — 이 구분이 성능과 안전을 가른다:

표기이름C++ 매핑특징
float64[3]고정 크기std::array<double,3>크기 불변. 힙 할당 없음.
float64[<=10]유한(bounded)rosidl_runtime_cpp::BoundedVector최대 크기 보장. 상한이 계약이 됨.
float64[]무한(unbounded)std::vector<double>제한 없음. 동적 할당.

문자열도 같은 방식으로 한계를 준다.

string<=32 device_id            # 최대 32바이트
string<=16[<=8] tags            # 최대 8개, 각 최대 16바이트

왜 한계를 지정하는가 — 이것이 실무적으로 중요한 이유:

  1. 무할당 경로(zero-allocation)의 전제. 실시간 루프에서 힙 할당은 예측 불가능한 지연을 만든다(12.3). 메시지가 전부 고정·유한이면 크기가 컴파일 시점에 상한이 정해지고, 미리 할당해 둔 풀에서 꺼내 쓸 수 있다.
  2. loaned message·zero-copy의 전제. 공유 메모리 전송(2.7)은 고정 크기 메시지(plain old data) 에서 가장 효과가 크다. 무한 배열이 하나라도 있으면 이 경로가 막히거나 효율이 떨어진다.
  3. 방어. 상한이 없으면 악의적·버그성 거대 메시지가 수신 측 메모리를 소모한다(13.1).
  4. DDS 자원 계획. 유한 크기를 알면 DDS가 버퍼를 미리 할당할 수 있다.

함정: 이론은 위와 같지만 실제 이득은 RMW 구현과 QoS 설정에 달렸다. "유한 배열을 썼으니 할당이 없다"는 보장은 아니다 — 특히 Python(rclpy)에서는 어차피 객체 할당이 일어난다. 무할당 경로의 실제 조건은 2.6·12.3에서 다루며, 여기서는 "실시간을 노린다면 처음부터 유한하게 쓰라" 는 원칙만 세운다. 나중에 무한→유한으로 바꾸는 것은 호환을 깨는 변경이다. ((확인 필요): 유한 배열이 실제 RMW에서 어떤 자원 계획으로 이어지는지는 벤더마다 다르다 — 3.6.)

상수:

# 상수는 대문자, '=' 사용
uint8 MODE_IDLE=0
uint8 MODE_RUN=1
uint8 MODE_FAULT=2

uint8 mode          # 위 상수 중 하나

C++에서는 MotorState::MODE_RUN, Python에서는 MotorState.MODE_RUN으로 접근한다. 열거형이 없으므로 이 관용구가 사실상의 enum이다. 상수와 필드를 같은 파일에 두고 주석으로 관계를 명시하는 것이 관행이다.

함정: 기본값과 상수를 혼동하지 말 것. uint8 mode 0은 기본값(초기화 값), uint8 MODE_IDLE=0은 상수(변경 불가 심볼)다. 문법이 비슷해 실수하기 쉽다.

1.2.4 srv와 action의 구조

# SetMode.srv
uint8 MODE_IDLE=0
uint8 MODE_RUN=1
uint8 mode
---
bool success
string message

--- 위가 요청, 아래가 응답. 응답에 bool success + string message를 두는 것은 std_srvs/srv/Trigger에서 온 강한 관행이며, 에러를 예외가 아니라 데이터로 돌려주는 방식이다. 서비스에는 별도 에러 채널이 없으므로(1.7) 이 관행을 따르는 편이 좋다.

# Calibrate.action
# --- 목표(Goal) ---
uint8 axis
float64 timeout_sec
---
# --- 결과(Result) ---
bool success
float64 offset
string message
---
# --- 피드백(Feedback) ---
float64 progress      # 0.0 ~ 1.0
string stage

액션은 ---로 세 부분을 나눈다. 생성되는 타입은 Calibrate::Goal, Calibrate::Result, Calibrate::Feedback이며, 그 외에 액션 프로토콜용 내부 타입(SendGoal·GetResult·FeedbackMessage)이 자동으로 만들어진다 — 액션 하나가 실제로는 서비스 3개 + 토픽 2개로 구현되기 때문이다(1.8). 실물로 확인했다(85차, 실행 확인): 액션 서버 하나를 실행하면 <이름>/_action/send_goal·cancel_goal·get_result 서비스 셋과 <이름>/_action/feedback·status 토픽 둘이 정확히 그만큼 생긴다.

함정: 피드백에 대용량 데이터(이미지·포인트클라우드)를 넣지 말 것. 피드백은 자주 흐르고, 액션 클라이언트가 여럿이면 그만큼 곱해진다. 대용량 데이터는 별도 토픽으로 보내고 피드백에는 참조(프레임 번호·타임스탬프)만 싣는 것이 관행이다.

1.2.5 인터페이스 패키지의 빌드 구조

CMakeLists.txt:

cmake_minimum_required(VERSION 3.8)
project(my_interfaces)

find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(std_msgs REQUIRED)           # 중첩해 쓰는 패키지
find_package(builtin_interfaces REQUIRED)

rosidl_generate_interfaces(${PROJECT_NAME}
  "msg/MotorState.msg"
  "srv/SetMode.srv"
  "action/Calibrate.action"
  DEPENDENCIES std_msgs builtin_interfaces
)

ament_export_dependencies(rosidl_default_runtime)
ament_package()

package.xml:

<buildtool_depend>ament_cmake</buildtool_depend>
<buildtool_depend>rosidl_default_generators</buildtool_depend>

<depend>std_msgs</depend>
<depend>builtin_interfaces</depend>

<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>

<member_of_group>rosidl_interface_packages</member_of_group>가 빠지는 것이 가장 흔한 빌드 실패 원인이다. 이 선언이 있어야 생성기들이 이 패키지를 인터페이스 패키지로 인식한다.

같은 패키지에서 인터페이스와 노드를 함께 빌드할 때 링크가 필요하다.

# 같은 패키지 안에서 자기가 만든 타입을 쓰려면
rosidl_get_typesupport_target(cpp_typesupport_target
  ${PROJECT_NAME} rosidl_typesupport_cpp)
target_link_libraries(my_node "${cpp_typesupport_target}")

견해: 인터페이스는 별도 패키지로 분리하는 것이 거의 항상 옳다(my_robot_interfaces). 이유 셋. ① 인터페이스를 쓰는 쪽이 로직 패키지의 규모가 큰 의존을 끌어오지 않는다. ② 인터페이스만 배포·공유할 수 있다(외부 팀·다른 언어 클라이언트). ③ 순환 의존을 구조적으로 막는다 — 인터페이스 패키지는 아무것도 의존하지 않는 잎(leaf)이 된다. 대가는 패키지 수 증가와 빌드 순서 의존이며, 이 정도는 비용이 낮다.

1.2.6 IDL — ROS 2 인터페이스의 실제 정체

.msg는 최종 형식이 아니다. 빌드 과정에서 rosidl_adapter가 .msg·.srv·.action을 OMG IDL(.idl) 로 변환하고, 그 다음부터 모든 생성기는 IDL을 입력으로 삼는다.

MotorState.msg rosidl_adapter MotorState.idl (OMG IDL 4.2) rosidl_generator_c/cpp/py rosidl_typesupport_* 구조체·헤더 직렬화·타입 지원

왜 IDL을 거치는가:

  1. DDS가 IDL 기반이다. DDS·RTPS의 타입 시스템이 OMG IDL이므로, 여기에 맞추면 DDS 벤더의 기존 도구 사슬을 그대로 쓸 수 있다.
  2. 표현력. .msg 문법은 단순해서 표현 못 하는 것이 있다(중첩 모듈, 어노테이션). IDL은 그것을 담는다.
  3. 비ROS 시스템과의 상호운용. IDL이 있으면 ROS를 쓰지 않는 DDS 애플리케이션도 같은 타입으로 통신할 수 있다 — 산업 현장에서 기존 DDS 인프라와 연결할 때 실제로 쓰인다.

.idl을 직접 쓸 수도 있다. rosidl_generate_interfaces에 .idl 파일을 그대로 넘기면 어댑터를 건너뛴다. .msg로 표현 못 하는 구조가 필요할 때(예: 특정 어노테이션) 쓴다. 다만 .msg가 정본인 편이 생태계 도구와 잘 맞으므로, 특별한 이유가 없으면 .msg를 쓴다.

정직: .idl 직접 사용은 문서화·도구 지원이 .msg보다 얇다. ros2 interface show가 어떻게 보여주는지 등 세부는 (확인 필요).

1.2.7 표준 인터페이스 — 만들기 전에 찾아라

커스텀 타입을 만드는 것은 비용이다. 표준 타입을 쓰면 RViz2가 바로 그려주고(11.4), rosbag2가 이해하고, 기존 노드가 연결된다. 커스텀 타입은 이 모든 것을 직접 해야 한다.

패키지대표 타입용도
std_msgsHeader, String, Bool, Float64원시 타입 래퍼는 쓰지 말 것(아래)
builtin_interfacesTime, Duration시간 표현
geometry_msgsPose, Twist, Transform, PoseStamped, TwistStamped, Wrench, Accel기하
sensor_msgsImage, CompressedImage, CameraInfo, PointCloud2, LaserScan, Imu, JointState, NavSatFix, BatteryState, Range센서(7.1)
nav_msgsOdometry, Path, OccupancyGrid, MapMetaData항법(8.1)
trajectory_msgsJointTrajectory, MultiDOFJointTrajectory궤적(6.5)
diagnostic_msgsDiagnosticArray, DiagnosticStatus진단(6.9)
shape_msgs, visualization_msgsSolidPrimitive, Marker, MarkerArray충돌·시각화
std_srvsTrigger, SetBool, Empty흔한 서비스
action_msgsGoalStatus, GoalInfo액션 내부(1.8)
lifecycle_msgsState, Transition라이프사이클(1.12)
rcl_interfacesParameterEvent, SetParametersResult, Log파라미터·로깅(1.9·1.11)
tf2_msgsTFMessagetf(5.1)
control_msgsJointJog, GripperCommand 등ros2_control(6.5)

std_msgs의 원시 래퍼(std_msgs/msg/Float64 등)를 쓰지 말라는 것은 ROS 2 커뮤니티의 명시적 권고다. 이유: Float64 하나만 보면 그것이 무엇의 값이고 어떤 단위이며 언제 측정된 것인지 알 수 없다. 의미 있는 타입을 만들거나(단위·프레임·타임스탬프 포함), 기존 의미 타입을 써야 한다. 디버깅·프로토타이핑에는 편리하지만 제품 인터페이스로 남기면 부채가 된다.

Header를 넣을 것인가:

std_msgs/Header header    # stamp(시간) + frame_id(좌표계)
  • 넣어야 하는 경우: 데이터가 특정 시점에 특정 좌표계에서 온 것일 때 — 사실상 모든 센서 데이터. tf2 변환(5.1)·시간 동기화(7.7)·bag 재생(11.3)이 전부 header.stamp에 의존한다.
  • 없어도 되는 경우: 시점·좌표계와 무관한 설정·명령·상태(모드 전환, 불리언 플래그).
  • 애매하면 넣어라. 나중에 추가하는 것은 호환을 깨는 변경이다.

geometry_msgs에 Pose와 PoseStamped가 둘 다 있는 이유가 이것이다. Stamped 접미어가 붙은 것은 헤더가 있는 버전이며, 로봇 시스템에서 노드 간 경계를 넘는 데이터는 대개 Stamped 쪽이 맞다.

함정: geometry_msgs/msg/Twist에는 헤더가 없다. /cmd_vel이 Twist인 것은 역사적 이유이며, 그래서 "이 속도 명령이 어느 프레임 기준인가"가 메시지 안에 없다 — 관행(base_link)에 의존한다. Nav2·ros2_control 생태계 일부는 TwistStamped로 옮겨가는 중이며, Jazzy 시점에서 패키지마다 어느 쪽을 쓰는지 갈린다(확인 필요). 통합 시 반드시 확인할 지점이다(8.5·6.5).

1.2.8 인터페이스 진화 — 호환을 깨지 않고 바꾸기

결론부터: ROS 2에서 메시지 정의 변경은 기본적으로 파괴적이다. ROS 1의 MD5 체크섬과 마찬가지로 ROS 2도 타입 정합성을 검사하며, Jazzy에서는 type hash(RIHS01) 로 이를 노출한다(1.3). 필드를 하나만 추가해도 해시가 바뀌고, 구버전·신버전 노드는 서로를 다른 타입으로 본다.

예 주석·기본값만 메시지 정의 변경 필드 추가·삭제· 타입 변경·순서 변경? type hash 변경 = 호환 불가 와이어 호환 유지 (확인 필요) 전 노드 동시 재빌드· 재배포 또는 새 타입 이름으로 병행

전략 ① 전면 교체(빅뱅) — 모든 노드를 함께 재빌드·재배포.

  • 장점: 단순, 부채 없음.
  • 단점: 무중단 배포 불가. 필드에 나간 로봇을 한꺼번에 업그레이드해야 한다. 기존 bag 파일이 새 코드로 안 읽힌다.
  • 맞는 경우: 단일 팀·단일 배포 단위, 개발 초기.

전략 ② 병행(v2 타입 신설) — MotorState2 또는 my_interfaces_v2/msg/MotorState를 새로 만들고, 두 타입을 잠시 함께 발행.

  • 장점: 무중단 이행. 소비자가 각자 속도로 옮긴다.
  • 단점: 대역·CPU 이중 소모, 브리지 노드 필요, 정리 시점을 정해야 한다.
  • 맞는 경우: 다수 팀·외부 소비자·필드 배포 중.

전략 ③ 확장 여지를 미리 둔다 — 처음부터 string[] extra_keys / string[] extra_values 같은 확장 필드나 자유 형식 문자열을 둔다.

  • 장점: 소소한 추가에 재빌드가 필요 없다.
  • 단점: 타입 안전을 스스로 포기하는 것. 스키마가 문서 밖으로 새고, 검증이 런타임으로 미뤄진다. 남용하면 "JSON을 메시지 안에 넣은" 상태가 되어 ROS 2 타입 시스템의 이점이 사라진다.
  • 맞는 경우: 진단·메타데이터처럼 핵심 제어 경로가 아닌 곳에 한정.

견해: 기본은 ①, 외부 소비자가 있으면 ②. ③은 진단·로깅 계열에만 제한적으로 쓴다. 그리고 어느 쪽이든 초기 설계에 시간을 더 쓰는 것이 가장 비용이 낮다 — 인터페이스는 코드보다 오래 유지된다. 특히 Header 포함 여부, 단위(SI — REP 103), 배열 한계, 상수 체계는 처음에 정하고 시작하라.

bag 호환은 별개 문제다. rosbag2는 기록 시점의 메시지 정의를 파일에 함께 저장하므로 재생 자체는 가능하지만, 새 정의로 컴파일된 노드가 옛 데이터를 역직렬화하려 하면 깨진다. 장기 데이터셋을 유지해야 한다면 이 점이 전략 선택을 지배한다(11.2·11.3).

1.2.9 설계 판단 — 굵은 메시지 vs 잔 메시지

같은 정보를 한 메시지로 묶을 것인가, 여러 토픽으로 분할할 것인가.

① 묶는다(굵은 메시지) — RobotState 하나에 관절·배터리·모드·오류를 전부.

  • 장점: 원자성. 한 시점의 일관된 스냅숏이 보장된다. 구독자가 시간 정렬을 안 해도 된다(7.7의 message_filters가 불필요).
  • 단점: 일부만 필요한 구독자도 전부 받는다(대역·CPU 낭비). 한 필드의 갱신 주기가 다른 필드를 끌고 간다. 변경 시 영향 범위가 넓다.

② 분할한다(잔 메시지) — /joint_states, /battery, /mode를 각각.

  • 장점: 필요한 것만 구독. 주기·QoS를 항목별로 다르게(배터리 1 Hz TRANSIENT_LOCAL, 관절 500 Hz BEST_EFFORT — 1.6). 변경 격리.
  • 단점: 시간 정렬 부담이 구독자에게 넘어간다. 토픽 수 증가 → 디스커버리·엔티티 비용 증가(3.3).
판단 축묶기 유리분할 유리
소비자가 항상 전부 필요한가예아니오
항목별 주기가 같은가같다크게 다르다
항목별 QoS가 같은가같다다르다
원자적 일관성이 필요한가필요불필요
항목이 자주 추가되는가아니오예

견해: 제어에 쓰이는 상태는 묶고(원자성), 관측·기록용은 분할한다를 기본으로 본다. sensor_msgs/msg/JointState가 관절 전체를 하나로 묶은 것은 원자성 때문이고, 진단이 별도 토픽인 것은 주기·소비자가 다르기 때문이다. 두 방식은 배타적이지 않다 — 제어용 묶음 토픽과 관측용 분리 토픽을 함께 두는 시스템도 흔하다.

1.2.10 인터페이스 검사 명령

ros2 interface list                              # 설치된 전체
ros2 interface packages                          # 인터페이스를 가진 패키지
ros2 interface package sensor_msgs               # 그 패키지의 타입 목록
ros2 interface show sensor_msgs/msg/Imu          # 정의(중첩 펼침)
ros2 interface proto sensor_msgs/msg/Imu         # 채워 넣을 YAML 뼈대

ros2 interface show는 중첩 타입을 펼쳐 보여주므로 실제 필드 구조를 파악하는 데 가장 빠르다. proto는 ros2 topic pub·ros2 service call의 인자를 직접 만들 때 쓴다(0.7.7).

1.2.11 함정 카탈로그

#함정증상대처
1<member_of_group>rosidl_interface_packages</member_of_group> 누락코드 생성 안 됨·링크 실패package.xml에 추가
2파일명 snake_case빌드 실패 또는 비정상적인 타입 이름UpperCamelCase
3인터페이스와 노드를 같은 패키지에 두고 링크 누락자기 타입을 못 찾음rosidl_get_typesupport_target
4인터페이스 패키지가 로직 패키지를 의존순환 의존·빌드 교착인터페이스 패키지는 잎으로
5std_msgs/msg/Float64를 제품 인터페이스로의미·단위·시각 불명의미 있는 타입 정의
6Header 누락 후 나중에 추가전체 재배포 필요애매하면 처음부터 포함
7무한 배열만 사용실시간 경로에서 할당 지터유한 배열·문자열(12.3)
8기본값과 상수 혼동의도와 다른 값uint8 x 0 vs uint8 X=0
9액션 피드백에 이미지 탑재대역 폭증참조만 싣고 별도 토픽
10필드 순서 변경을 "무해"하다고 판단와이어 포맷·해시 변경순서도 계약의 일부(1.3)
11Twist vs TwistStamped 혼용통합 시 타입 불일치로 구독이 매칭 안 됨Jazzy diff_drive_controller는 TwistStamped만 받는다(6.5.12.1)
12단위를 문서화하지 않음deg/rad·mm/m 혼동 사고REP 103(SI) 준수 + 주석

1.2.12 체크포인트

  • .msg의 배열 세 종류를 쓰고, 유한 배열이 실시간·zero-copy와 어떻게 연결되는지 설명할 수 있는가. 그 이득이 보장인지 가능성인지 구분할 수 있는가.
  • 기본값 문법과 상수 문법을 즉시 구분해 쓸 수 있는가.
  • ROS 2가 .msg를 IDL로 변환하는 이유 세 가지를 말할 수 있는가.
  • 인터페이스를 별도 패키지로 분리하는 이유 세 가지와, 그 대가를 말할 수 있는가.
  • std_msgs/msg/Float64를 제품 인터페이스로 쓰지 말라는 권고의 근거를 설명할 수 있는가.
  • 어떤 메시지에 Header를 넣을지 판단 기준을 세울 수 있는가. Twist에 헤더가 없어서 생기는 실무 문제는 무엇인가.
  • 이미 배포된 메시지에 필드를 추가해야 할 때, ①②③ 전략 중 무엇을 고르겠는가. 기존 bag 파일이 그 판단에 어떤 영향을 주는가.
  • 상태 정보를 굵은 메시지 하나로 묶을지 여러 토픽으로 분할할지, 판단 축 다섯 가지로 설명할 수 있는가.

다음 장 1.3에서는 이 정의가 코드와 바이트열이 되는 과정 — rosidl 코드 생성, 타입 지원(typesupport), CDR 직렬화, 그리고 Jazzy에서 노출된 type hash — 를 연다.