학습 목표
- Message가 Node 사이에서 공유하는 자료형 계약인 이유를 설명할 수 있다.
.msg의 기본형, 배열, 상한, 기본값, 상수와 중첩 Message를 읽을 수 있다.ros2 interface와ros2 topic echo로 처음 보는 Message를 스스로 조사할 수 있다.Header.stamp와frame_id를 실제 측정 시각과 좌표계에 맞게 설정할 수 있다.- 사용자 정의 Message Package를 만들고 Python Publisher·Subscriber에서 사용할 수 있다.
LaserScan,Image,PointCloud2,Odometry의 핵심 Field를 해석할 수 있다.- Message 크기, 발행 주기와 Subscriber 수로 통신 부하를 추정할 수 있다.
- 실제 Robot에서 Type, 값, 시간, Frame과 단위를 순서대로 진단할 수 있다.
1. Message는 데이터가 아니라 계약이다
Topic은 Data가 흐르는 이름 있는 통로이고, Message는 그 통로에서 교환할 자료형 계약입니다. 계약에는 Field 이름, Type, 배열 길이와 중첩 구조가 들어갑니다. Publisher와 Subscriber는 Topic 이름만 같아서는 부족하고 같은 ROS Interface Type과 호환되는 QoS를 사용해야 합니다.
Publisher
└─ sensor_msgs/msg/LaserScan Instance
├─ header
├─ angle_min
├─ angle_increment
└─ ranges[]
↓ Serialization · Middleware · Deserialization
Subscriber
└─ 같은 sensor_msgs/msg/LaserScan 계약으로 해석
Field 구성이 우연히 같더라도 Package와 Type 이름이 다르면 다른 계약입니다. geometry_msgs/msg/Twist와 직접 만든 my_robot_msgs/msg/Twist는 Field가 같아도 연결되지 않습니다. 반대로 Type이 같아도 단위나 의미를 다르게 해석하면 통신은 성공하지만 Robot은 잘못 동작합니다.
Message는 구조를 정의하지만 모든 의미를 자동 보장하지 않습니다. 거리 단위, 축 방향, 값 범위, Timestamp 기준과 오류 표현은 주석·표준·Package 문서에 명시해야 합니다.
ROS 2 공식 문서는 .msg, .srv, .action을 ROS Interface로 설명하며, 정의에서 여러 언어용 Source와 Type Support를 생성합니다. ROS 2 Interfaces
2. ROS Interface 세 종류
| Interface | 파일 | 구성 | 대표 용도 |
|---|---|---|---|
| Message | .msg |
하나의 Data 구조 | Topic Stream, Service·Action 내부 자료형 |
| Service | .srv |
Request --- Response |
짧은 요청과 한 번의 응답 |
| Action | .action |
Goal --- Result --- Feedback |
오래 걸리고 취소·진행률이 필요한 작업 |
# SaveMap.srv
string map_name
bool overwrite
---
bool success
string message
# Dock.action
float32 approach_speed
---
bool docked
float32 final_error
---
float32 remaining_distance
string phase
Service와 Action도 결국 여러 Message 선언을 조합합니다. 따라서 .msg를 정확히 읽는 능력은 ROS 통신 전체의 기초입니다.
3. .msg 문법 읽기
3.1 기본 Field와 기본값
한 줄에 Type FieldName을 쓰며 # 뒤는 주석입니다.
bool enabled
int32 sample_count
float32 temperature
float64 voltage 0.0
string sensor_name "front_lidar"
주요 기본 Type은 다음과 같습니다.
| 종류 | Type | 설계 확인 사항 |
|---|---|---|
| 논리 | bool |
true·false의 의미 |
| 부호 정수 | int8~int64 |
범위와 음수 허용 여부 |
| 부호 없는 정수 | uint8~uint64 |
Counter Overflow |
| 실수 | float32, float64 |
정밀도, NaN·Infinity 허용 여부 |
| 문자 | string, wstring |
최대 길이와 Encoding |
| Byte 계열 | byte, char |
숫자와 Raw Data 의미 구분 |
3.2 배열과 상한
int32[] samples # 길이 제한 없는 가변 배열
int32[5] calibration_values # 정확히 5개인 고정 배열
int32[<=100] recent_samples # 최대 100개인 가변 배열
string<=32 robot_name # 최대 길이 32인 문자열
string<=16[<=8] joint_names # 최대 8개, 각 문자열 최대 16
상한은 입력 검증뿐 아니라 Worst-case Memory와 Serialized Size를 계산하는 데 유리합니다. 하지만 상한을 썼다는 사실만으로 Application 전체가 Real-time이 되는 것은 아닙니다.
3.3 상수와 중첩 Type
uint8 STATE_OK=0
uint8 STATE_WARNING=1
uint8 STATE_ERROR=2
std_msgs/Header header
geometry_msgs/Pose pose
uint8 state
상수는 Message Instance마다 전송되는 Field가 아니라 생성된 Type에서 참조하는 고정값입니다. Python에서는 보통 RobotState.STATE_ERROR처럼 사용합니다.
4. 좋은 Message를 설계하는 기준
좋은 Message는 단순히 Build되는 Message가 아니라 서로 다른 팀과 Robot이 오해 없이 사용할 수 있는 계약입니다.
- 이름에 단위 대신 의미를 우선합니다. 단위는 표준과 주석으로 고정합니다.
- SI 단위와 REP-103 축 규약을 사용합니다.
percentage가0.0~1.0인지0~100인지 명시합니다.- 유효하지 않은 값의 표현을 정합니다.
NaN, 별도validField, Status Code 중 하나를 선택합니다. - Sensor Data에는 측정 Timestamp와 Frame을 포함합니다.
- 무제한 배열과 문자열이 정말 필요한지 검토합니다.
- 동시에 변해야 하는 값은 하나의 Message로 묶습니다.
- 큰 Binary Data를 YAML 문자열이나 숫자 배열로 임의 포장하지 않습니다.
# my_robot_msgs/msg/BatteryStatus.msg
uint8 LEVEL_OK=0
uint8 LEVEL_LOW=1
uint8 LEVEL_CRITICAL=2
std_msgs/Header header
float32 voltage # V
float32 current # A, 방전 시 음수
float32 percentage # 0.0~1.0
float32[<=16] cell_voltages # V
uint8 level
bool charging
기존 표준 Message가 있다면 새 Type을 만들기 전에 재사용을 검토합니다. 임의 Message가 늘어나면 Driver, Visualization, Bag 분석과 외부 Package 연결 비용도 늘어납니다.
5. Header: 언제, 어느 좌표계인가
std_msgs/msg/Header는 두 Field로 구성됩니다.
builtin_interfaces/Time stamp
string frame_id
5.1 stamp는 측정 시각이다
Sensor Message의 Stamp는 일반적으로 Callback 수신 시각이 아니라 Data가 유효해진 측정 시각입니다. LiDAR가 10.000초에 측정한 Scan이 Network와 Driver를 거쳐 10.100초에 도착했다면, 수신 시각을 Stamp로 넣을 경우 100 ms 지연이 숨겨집니다.
t=10.000 LiDAR 측정 ───── 100 ms ─────> t=10.100 Subscriber 수신
올바른 stamp=10.000 잘못된 stamp=10.100
TF2와 Sensor Fusion은 Stamp 시점의 자세를 사용합니다. 움직이는 Robot에서 잘못된 Stamp는 벽이 겹쳐 보이거나 Point Cloud가 흔들리고 Localization이 불안정해지는 원인이 됩니다.
5.2 frame_id는 표현 기준이다
frame_id는 Data가 어느 좌표계에 표현되었는지를 나타냅니다. 예를 들어 Scan은 laser_frame, Camera Image는 camera_optical_frame, Odometry Pose는 보통 Message가 정의한 Header Frame을 따릅니다. ROS 2 Frame 이름 앞에 /를 관성적으로 붙이지 않습니다.
5.3 ROS Time과 Simulation Time
Node에서 현재 시각이 필요하면 OS 함수를 직접 섞기보다 Node Clock을 사용합니다.
msg.header.stamp = self.get_clock().now().to_msg()
msg.header.frame_id = 'battery_link'
Simulation에서는 use_sim_time:=true인 Node가 /clock을 따릅니다. 일부 Node만 System Time을 사용하면 Timestamp 영역이 달라져 TF 조회와 Sensor 동기화가 실패합니다.
ros2 param get /battery_publisher use_sim_time
ros2 topic echo /clock --once
6. CLI로 처음 보는 Message 조사하기
6.1 설치된 Interface 찾기
ros2 interface list
ros2 interface list | less
ros2 interface packages
ros2 interface package sensor_msgs
6.2 Type 정의와 발행용 Prototype 확인
ros2 interface show std_msgs/msg/Header
ros2 interface show geometry_msgs/msg/Twist
ros2 interface show sensor_msgs/msg/LaserScan
ros2 interface proto geometry_msgs/msg/Twist
6.3 실행 중인 Topic에서 Type 찾기
ros2 topic list -t
ros2 topic type /scan
ros2 topic info /scan --verbose
6.4 실제 값 확인
ros2 topic echo /scan --once
ros2 topic echo /scan --no-arr
ros2 topic echo /scan --field header
ros2 topic echo /scan --field angle_increment
ros2 topic echo /odom --field pose.pose.position
ROS 배포판에 따라 CLI Option이 다를 수 있으므로 다음 명령으로 설치된 Version의 정확한 Option을 확인합니다.
ros2 interface --help
ros2 interface show --help
ros2 topic echo --help
대용량 배열을 무조건
echo하지 마십시오. Image나 PointCloud2의data는 Terminal을 채우고 CLI 자체가 부하를 만들 수 있습니다. 먼저--no-arr또는--field로 Metadata를 확인합니다.
7. 사용자 정의 Message Package 만들기
Interface는 일반적으로 전용 ament_cmake Package에 둡니다. Python Application Package와 분리하면 C++, Python 및 여러 Node가 같은 계약을 재사용하기 쉽습니다.
7.1 Package와 파일 생성
mkdir -p ~/ros2_ws/src
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake my_robot_msgs
mkdir -p my_robot_msgs/msg
my_robot_msgs/msg/BatteryStatus.msg를 앞 절의 내용으로 저장합니다.
7.2 CMakeLists.txt
cmake_minimum_required(VERSION 3.8)
project(my_robot_msgs)
find_package(ament_cmake REQUIRED)
find_package(rosidl_default_generators REQUIRED)
find_package(std_msgs REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/BatteryStatus.msg"
DEPENDENCIES std_msgs
)
ament_export_dependencies(rosidl_default_runtime)
ament_package()
7.3 package.xml 핵심 의존성
<buildtool_depend>ament_cmake</buildtool_depend>
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<depend>std_msgs</depend>
<member_of_group>rosidl_interface_packages</member_of_group>
7.4 Build와 확인
cd ~/ros2_ws
rosdep install --from-paths src --ignore-src -r -y
colcon build --packages-select my_robot_msgs --symlink-install
source install/setup.bash
ros2 interface show my_robot_msgs/msg/BatteryStatus
ros2 interface proto my_robot_msgs/msg/BatteryStatus
ros2 interface package my_robot_msgs
새 Terminal마다 Workspace를 Source하지 않으면 Build가 성공했어도 Interface를 찾지 못합니다.
8. Python에서 사용자 Message 발행하기
Application Package를 만듭니다.
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_python battery_monitor \
--dependencies rclpy my_robot_msgs
battery_monitor/battery_monitor/battery_publisher.py:
import math
import rclpy
from rclpy.node import Node
from my_robot_msgs.msg import BatteryStatus
class BatteryPublisher(Node):
def __init__(self):
super().__init__('battery_publisher')
self.publisher = self.create_publisher(
BatteryStatus, 'battery/status', 10
)
self.timer = self.create_timer(1.0, self.publish_status)
self.percentage = 0.82
def publish_status(self):
msg = BatteryStatus()
msg.header.stamp = self.get_clock().now().to_msg()
msg.header.frame_id = 'battery_link'
msg.voltage = 24.6
msg.current = -1.8
msg.percentage = self.percentage
msg.cell_voltages = [4.10, 4.11, 4.09, 4.10, 4.11, 4.09]
msg.charging = False
if not math.isfinite(msg.percentage):
msg.level = BatteryStatus.LEVEL_CRITICAL
elif msg.percentage <= 0.10:
msg.level = BatteryStatus.LEVEL_CRITICAL
elif msg.percentage <= 0.25:
msg.level = BatteryStatus.LEVEL_LOW
else:
msg.level = BatteryStatus.LEVEL_OK
self.publisher.publish(msg)
self.get_logger().info(
f'battery={msg.percentage:.0%}, voltage={msg.voltage:.1f} V'
)
def main(args=None):
rclpy.init(args=args)
node = BatteryPublisher()
try:
rclpy.spin(node)
finally:
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
setup.py의 console_scripts에 등록합니다.
'battery_publisher = battery_monitor.battery_publisher:main',
Build하고 실행합니다.
cd ~/ros2_ws
colcon build --packages-select my_robot_msgs battery_monitor --symlink-install
source install/setup.bash
ros2 run battery_monitor battery_publisher
다른 Terminal에서 확인합니다.
source ~/ros2_ws/install/setup.bash
ros2 topic echo /battery/status --once
ros2 topic echo /battery/status --field percentage
9. Subscriber에서 값·시간·Frame 검증하기
Type이 맞는다는 것은 Data가 안전하다는 뜻이 아닙니다. Consumer 경계에서 값 범위와 시간 지연을 확인합니다.
import math
import rclpy
from rclpy.node import Node
from rclpy.time import Time
from my_robot_msgs.msg import BatteryStatus
class BatteryGuard(Node):
def __init__(self):
super().__init__('battery_guard')
self.subscription = self.create_subscription(
BatteryStatus, 'battery/status', self.on_status, 10
)
def on_status(self, msg: BatteryStatus):
stamp = Time.from_msg(msg.header.stamp)
age_sec = (self.get_clock().now() - stamp).nanoseconds / 1e9
if msg.header.frame_id != 'battery_link':
self.get_logger().error(f'unexpected frame: {msg.header.frame_id!r}')
return
if not math.isfinite(msg.percentage) or not 0.0 <= msg.percentage <= 1.0:
self.get_logger().error(f'invalid percentage: {msg.percentage}')
return
if age_sec < -0.1 or age_sec > 2.0:
self.get_logger().warning(f'stale or future message: age={age_sec:.3f}s')
if msg.level == BatteryStatus.LEVEL_CRITICAL:
self.get_logger().error('critical battery: request safe stop')
def main(args=None):
rclpy.init(args=args)
node = BatteryGuard()
try:
rclpy.spin(node)
finally:
node.destroy_node()
rclpy.shutdown()
실제 Safety Stop은 Log 출력만으로 끝내지 말고 Robot Architecture에 맞는 상태 전이, Controller Interface 또는 독립 Safety 계층으로 연결해야 합니다.
10. 자주 만나는 표준 Message 해석
10.1 geometry_msgs/msg/Twist
Vector3 linear
Vector3 angular
이 Message 자체에는 Header가 없습니다. 어느 Frame 기준인지 Interface Definition만으로 알 수 없으므로 /cmd_vel을 받는 Controller의 계약을 확인해야 합니다. Timestamp가 필요한 System은 TwistStamped 같은 Stamped Type 사용을 검토합니다.
10.2 sensor_msgs/msg/LaserScan
핵심 관계는 다음과 같습니다.
beam_angle(i) = angle_min + i × angle_increment
range(i) = ranges[i]
for index, distance in enumerate(msg.ranges):
angle = msg.angle_min + index * msg.angle_increment
range_min보다 작은 값과 range_max보다 큰 값의 처리 규칙을 Message 문서와 Driver 설명에서 확인하고, NaN과 Infinity를 무조건 장애물 거리로 사용하지 않습니다.
10.3 sensor_msgs/msg/Image
height, width, encoding, step, data가 핵심입니다. step은 한 행의 Byte 수이며 항상 단순히 width × channel이라고 가정하면 Padding이나 Encoding에 따라 틀릴 수 있습니다. 직접 Byte 배열을 해석하기보다 cv_bridge와 image_transport 같은 검증된 도구를 우선 사용합니다.
10.4 sensor_msgs/msg/PointCloud2
PointCloud2는 data Byte 배열만 보면 의미를 알 수 없습니다.
| Field | 의미 |
|---|---|
height, width |
Organized 또는 Unorganized Point 수 구조 |
fields |
x·y·z·intensity 등의 이름, Offset, Type, Count |
point_step |
Point 하나의 Byte 크기 |
row_step |
한 행의 Byte 크기 |
is_bigendian |
Byte 순서 |
data |
실제 Packed Binary Data |
is_dense |
유효하지 않은 Point 포함 여부 정보 |
직접 Offset 계산을 복제하기보다 sensor_msgs_py.point_cloud2 또는 C++ Iterator를 사용합니다.
from sensor_msgs_py import point_cloud2
for x, y, z in point_cloud2.read_points(
msg, field_names=('x', 'y', 'z'), skip_nans=True
):
process_point(float(x), float(y), float(z))
10.5 nav_msgs/msg/Odometry
Odometry는 Pose와 Twist뿐 아니라 Frame 관계와 Covariance를 포함합니다. 값만 보지 말고 header.frame_id, child_frame_id, Stamp와 Covariance가 Estimator의 기대와 맞는지 확인합니다.
11. 직렬화와 Type Support
.msg 파일은 실행 중에 그대로 전송되지 않습니다. Build 과정에서 C++, Python 등 언어별 Data Type과 Middleware가 사용하는 Type Support가 생성됩니다.
BatteryStatus.msg
↓ rosidl generator
Python Class · C++ Struct · Introspection Metadata
↓ Type Support
Serialization → DDS/RMW Transport → Deserialization
Python의 from my_robot_msgs.msg import BatteryStatus가 가능한 이유도 생성된 Python Module이 Install되기 때문입니다. Underlay·Overlay Source 순서가 틀리거나 Interface Package를 다시 Build하지 않으면 오래된 생성 Code를 Import할 수 있습니다. ROS 2 내부 Interface 구조는 공식 문서에서 rosidl Generator와 Type Support의 관계를 설명합니다. Internal ROS 2 interfaces
12. Message 크기와 대역폭 계산
첫 근사값은 다음과 같습니다.
Payload bandwidth ≈ Serialized message size × Hz × Network subscriber copies
640×480 RGB8 원본 Image의 Pixel Data는 다음과 같습니다.
640 × 480 × 3 = 921,600 byte/message
921,600 × 30 Hz = 27,648,000 byte/s ≈ 221.2 Mbit/s
여기에는 Header, Field Alignment, Serialization와 Network Protocol Overhead가 빠져 있으므로 실제 사용량은 더 클 수 있습니다. 같은 Process의 Intra-process 통신, Middleware 구현, Shared Memory와 Network Topology에 따라 복사와 Wire Traffic도 달라집니다. Subscriber 수를 무조건 곱한 값은 안전 측 설계 추정치이지 모든 배치에서 정확한 Packet 수는 아닙니다.
from dataclasses import dataclass
@dataclass(frozen=True)
class Stream:
name: str
bytes_per_message: int
hz: float
remote_subscribers: int = 1
@property
def payload_mbps(self) -> float:
return (
self.bytes_per_message
* self.hz
* self.remote_subscribers
* 8
/ 1_000_000
)
streams = [
Stream('rgb8_640x480', 640 * 480 * 3, 30.0, 2),
Stream('lidar_360', 1_600, 10.0, 2),
Stream('odometry', 800, 50.0, 2),
]
for stream in streams:
print(f'{stream.name:18s} {stream.payload_mbps:8.2f} Mbit/s')
print('total', sum(s.payload_mbps for s in streams), 'Mbit/s')
실행 중에는 계산값과 함께 실제 수신량도 측정합니다.
ros2 topic bw /camera/image_raw
ros2 topic hz /camera/image_raw --window 100
ros2 topic bw /points
대책은 해상도·주기 축소, ROI, 압축 Transport, Point Cloud Voxel Downsampling, 불필요한 Remote Subscriber 제거와 Process 배치 검토입니다. /cmd_vel 같은 제어 Stream과 대용량 Image가 같은 불안정한 Wi-Fi 경로를 경쟁하게 두지 않는 것도 중요합니다.
13. Interface 변경과 호환성
배포된 Message에 Field를 추가·삭제하거나 Type과 의미를 바꾸는 것은 통신 계약 변경입니다. Source만 바꾸고 한 Package만 Build하면 다른 Robot이나 Bag과 호환되지 않을 수 있습니다.
- Producer와 Consumer를 함께 Inventory합니다.
- Bag, Bridge, Dashboard와 Cloud Consumer도 포함합니다.
- 기존 Field의 단위나 의미를 조용히 바꾸지 않습니다.
- 큰 변경은
BatteryStatusV2처럼 새 Type이나 새 Topic으로 전환하는 방안을 검토합니다. - 전환 기간에는 Adapter Node를 두고 양쪽을 검증합니다.
- Interface Package Version과 Release Note를 관리합니다.
Recorded Bag에는 당시 Type 정보와 Serialized Data가 들어갑니다. 장기 재현성이 필요하다면 Source Commit, Container, ROS Distribution, RMW 설정과 Custom Interface Package도 함께 보존합니다.
14. 실제 Robot Message 진단 절차
Sensor가 “안 된다”라고 바로 Driver를 수정하지 말고 다음 순서를 따릅니다.
1단계: 이름과 Type
ros2 topic list -t
ros2 topic type /scan
ros2 interface show sensor_msgs/msg/LaserScan
2단계: Endpoint와 QoS
ros2 topic info /scan --verbose
3단계: Metadata와 값 범위
ros2 topic echo /scan --no-arr --once
ros2 topic echo /scan --field header --once
ros2 topic echo /scan --field angle_increment --once
4단계: 시간과 Frame
ros2 run tf2_ros tf2_echo base_link laser_frame
ros2 param get /lidar_driver use_sim_time
5단계: 주기와 대역폭
ros2 topic hz /scan --window 100
ros2 topic bw /scan
6단계: 기록 후 재현
ros2 bag record /scan /tf /tf_static /odom
ros2 bag info rosbag2_*
| 증상 | Message 관점의 후보 원인 |
|---|---|
| Topic은 보이지만 Callback 없음 | Type 또는 QoS 불일치 |
| RViz에서 Sensor가 안 보임 | 빈·잘못된 frame_id, TF 부재, 오래된 Stamp |
| 벽이 흔들리거나 겹침 | 측정 Stamp 오류, Clock 불일치, Motion Distortion |
| 값은 오지만 Robot이 과격함 | 단위·축·범위 해석 오류 |
| Camera를 켜면 제어 지연 | 큰 Message, 과도한 Hz, Queue와 Network 병목 |
| Custom Message를 찾지 못함 | Build·Source·Dependency 또는 Overlay 순서 오류 |
15. 흔한 실수와 교정
- Field 이름만 보고 단위를 추측한다. Interface 문서와 REP를 확인합니다.
- 수신 시각을 Sensor Stamp로 덮어쓴다. Hardware·Driver 측 측정 시각을 보존합니다.
frame_id를 비우거나 임의로 바꾼다. TF Tree와 일치하는 Frame을 사용합니다.- PointCloud2
data를 직접 고정 Offset으로 읽는다.fields를 반영하는 Library를 사용합니다. - 가변 배열을 무제한으로 만든다. 실제 최대값과 Memory Budget을 검토합니다.
- Custom Interface를 Application Package마다 복제한다. 전용 Package에서 한 계약을 공유합니다.
- Interface를 변경하고 일부 Node만 Build한다. 모든 Producer·Consumer와 Bag 호환성을 검증합니다.
- 큰 Message에 Reliable과 큰 Depth를 주면 안전하다고 생각한다. 오래된 Data가 쌓여 지연이 커질 수 있습니다.
- Terminal 전체 배열 출력으로 진단한다. Metadata와 특정 Field부터 좁혀 봅니다.
- 전송 Payload 계산을 실제 Network 사용량으로 단정한다. Protocol Overhead와 배치 조건을 포함해 측정합니다.
16. 실습 체크리스트
- [ ]
ros2 topic list -t로 Topic과 Type을 함께 확인했다. - [ ]
ros2 interface show로 중첩 Field까지 읽었다. - [ ]
ros2 interface proto결과를 발행 YAML 작성에 활용했다. - [ ]
Header.stamp가 측정 시각인지 확인했다. - [ ]
frame_id와 TF 연결을 확인했다. - [ ] Custom Interface Package를 Build하고 Source했다.
- [ ] Python에서 생성된 Message Class와 상수를 사용했다.
- [ ] Subscriber에서 값 범위와 Data Age를 검증했다.
- [ ] 큰 배열은
--no-arr또는--field로 조사했다. - [ ]
hz와bw로 실제 Stream을 측정했다. - [ ] 단위, 축, NaN와 오류 상태 계약을 문서화했다.
- [ ] 실제 구동 전 Bag과 Simulation에서 Message를 검증했다.
17. 정리
ROS Message는 단순한 Container가 아니라 Node, 언어, Computer와 팀 사이의 계약입니다. Type이 같아야 통신할 수 있고, 단위·시간·Frame의 의미까지 같아야 Robot이 올바르게 움직입니다.
처음 보는 Message를 만나면 외우려 하지 말고 topic list -t → interface show → topic echo --no-arr·--field → info --verbose → hz·bw 순서로 조사하십시오. 사용자 Interface는 전용 Package에서 생성하고, Subscriber 경계에서 값·시간·Frame을 검증하십시오. 이 습관이 Sensor Fusion, Navigation과 실제 Robot 안전의 기반이 됩니다.
- Message메시지
- ROS Node 사이에서 교환할 Field 이름, Type과 구조를 정의한 자료형 계약입니다.
- Interface인터페이스
- ROS 통신 계약을 기술하는 Message, Service와 Action 정의를 함께 부르는 용어입니다.
- IDL인터페이스 정의 언어
- 언어와 Middleware에 독립적으로 통신 Data 구조를 기술하고 언어별 Code 생성의 입력이 되는 정의 형식입니다.
- Field필드
- Message 안에서 이름과 Type으로 정의되는 하나의 Data 항목입니다.
- Bounded Array상한 있는 배열
- 실제 길이는 변할 수 있지만 선언된 최대 원소 수를 넘을 수 없는 배열입니다.
- Constant메시지 상수
- Message Type에 고정값으로 생성되며 Instance Data Field처럼 매번 전송되지 않는 값입니다.
- Header헤더
- Timestamp와 Frame ID를 담아 Data가 언제 어느 좌표계에서 유효한지 표현하는 표준 Message입니다.
- Timestamp측정 시각
- Sensor Data나 상태가 유효해진 시점을 초와 나노초로 표현한 시간 정보입니다.
- Frame ID좌표계 식별자
- Message의 공간 Data가 어떤 좌표계를 기준으로 표현되었는지 나타내는 이름입니다.
- ROS TimeROS 시간
- System Time 또는 Simulation의 /clock을 Node 설정에 따라 일관되게 제공하는 ROS 시간 체계입니다.
- Serialization직렬화
- Memory의 Message Instance를 Middleware와 Network가 전달할 수 있는 Byte 표현으로 변환하는 과정입니다.
- Deserialization역직렬화
- 수신한 Byte 표현을 Subscriber가 사용할 언어별 Message 객체로 복원하는 과정입니다.
- Type Support타입 지원 코드
- 특정 Message를 Middleware가 생성, 조사, 직렬화하고 전달할 수 있도록 생성되는 Metadata와 함수입니다.
- LaserScan2D 라이다 스캔
- 각도 범위, 증가량과 거리 배열로 평면 거리 측정을 표현하는 Sensor Message입니다.
- Image Encoding영상 인코딩
- Image Data의 Channel 구성, Bit 깊이와 Pixel 표현 방식을 지정하는 문자열 계약입니다.
- PointCloud2점군 메시지
- Point Field Layout과 Packed Byte Data를 이용해 다양한 구조의 3D Point 집합을 전달하는 Message입니다.
- Point Step점 하나의 바이트 크기
- PointCloud2에서 연속한 두 Point Data 시작 위치 사이의 Byte 간격입니다.
- Row Step점군 한 행의 바이트 크기
- Organized PointCloud2에서 한 행 전체가 차지하는 Byte 수입니다.
- Covariance공분산
- Pose나 Velocity 추정치의 불확실성과 변수 사이 상관관계를 수치로 표현한 행렬입니다.
- Interface Compatibility인터페이스 호환성
- Producer, Consumer와 저장 Data가 같은 Message 계약을 일관되게 생성하고 해석할 수 있는 성질입니다.
연습 문제
- Topic 이름이 같고 Field 구조가 같아 보여도 서로 다른 Message Type이 연결되지 않는 이유는 무엇인가요?
.msg,.srv,.action의 구분자와 용도를 설명하세요.- 고정 배열, 무제한 가변 배열과 상한 있는 가변 배열의 문법과 차이를 쓰세요.
- Message 상수와 일반 Field는 전송 관점에서 어떻게 다른가요?
Header.stamp에 Callback 수신 시각을 넣으면 움직이는 Robot에서 어떤 문제가 생길 수 있나요?frame_id가 비어 있거나 TF Tree와 다르면 어떤 증상이 나타날 수 있나요?- 처음 보는
/pointsTopic의 Type과 구조를 확인하는 명령 순서를 쓰세요. - Image와 PointCloud2에서 전체 배열을 바로
echo하지 않아야 하는 이유와 대안을 설명하세요. - 사용자 Message Package에서
rosidl_default_generators와rosidl_default_runtime은 각각 어떤 단계에 필요한가요? - Build가 성공한 Custom Message를 새 Terminal에서 찾지 못할 때 가장 먼저 확인할 것은 무엇인가요?
- Subscriber가
percentage와 Timestamp를 검증해야 하는 이유를 설명하세요. - LaserScan의
ranges[i]에 대응하는 Beam Angle 계산식을 쓰세요. - PointCloud2를
data배열만 보고 해석할 수 없는 이유를 네 가지 Field와 함께 설명하세요. - 640×480 RGB8 Image를 30 Hz로 한 Remote Subscriber에게 보낼 때 Pixel Payload 대역폭을 계산하세요.
- 실제 Robot의 Sensor Message를 이름·Type부터 Bag 기록까지 진단하는 순서를 설명하세요.
COMMUNITY
강의 댓글
질문과 학습 경험을 함께 나눠보세요.댓글을 불러오는 중입니다.