Message trong ROS2 là gì? Cấu trúc dữ liệu chuẩn hoá
Atlasbot · 16/8/2026 · 11 phút đọc
Một topic không truyền "dữ liệu chung chung" — nó truyền đúng một kiểu message cố định, được định nghĩa tường minh trước khi biên dịch. Đây là lý do publisher viết bằng C++ và subscriber viết bằng Python vẫn hiểu đúng dữ liệu của nhau: cả hai cùng dùng chung một định nghĩa message, chỉ khác ngôn ngữ sinh code.
Khái niệm chính
Message được khai báo trong file .msg — cú pháp cực đơn giản, mỗi dòng là kiểu_dữ_liệu tên_trường:
# geometry_msgs/Twist.msg
Vector3 linear
Vector3 angular
# geometry_msgs/Vector3.msg
float64 x
float64 y
float64 z
Message có thể lồng nhau — Twist chứa hai Vector3 bên trong, mỗi Vector3 lại có 3 trường số thực. Các kiểu cơ bản có sẵn: int32, float64, string, bool, bool[] (mảng)... đủ để ghép thành hầu hết cấu trúc dữ liệu robot cần.
Message được biên dịch thành class thật
Khi build package, công cụ sinh code của ROS2 tự động đọc file .msg và sinh ra class tương ứng trong từng ngôn ngữ (Python, C++) — không cần viết tay struct/class thủ công, và không sợ hai bên định nghĩa lệch nhau vì cả hai cùng sinh ra từ một nguồn .msg duy nhất.
Tóm lại: File
.msglà "hợp đồng" mô tả hình dạng dữ liệu — publisher và subscriber dù viết bằng ngôn ngữ nào cũng phải tuân theo đúng hợp đồng đó, đảm bảo không bao giờ hiểu sai cấu trúc dữ liệu của nhau.
Nguyên lý hoạt động
Twist.msg (định nghĩa)
↓ biên dịch lúc build (rosidl)
Python: geometry_msgs.msg.Twist
C++: geometry_msgs::msg::Twist
↓
Dùng trong code như một class/struct bình thường
Tạo và điền dữ liệu vào một message trong Python:
from geometry_msgs.msg import Twist
msg = Twist()
msg.linear.x = 0.2 # truy cập trường lồng nhau như thuộc tính object
msg.angular.z = 0.1
publisher.publish(msg)
Xem cấu trúc đầy đủ của bất kỳ kiểu message nào (kể cả message từ package của người khác) mà không cần mở file .msg thủ công:
ros2 interface show geometry_msgs/msg/Twist
Kiến thức kỹ thuật
Công thức
Kích thước message khi truyền qua mạng ước lượng bằng tổng kích thước từng trường, cộng thêm overhead đóng gói (CDR, xem mục Register/Bit/Byte):
Kích thước message ≈ Σ(kích thước từng trường) + overhead CDR
VD: Twist (2 × Vector3, mỗi Vector3 = 3 × float64)
= 2 × (3 × 8 byte) + overhead ≈ 48 byte + ~20 byte overhead ≈ ~68 byte
Con số này giải thích trực tiếp phần "kích thước 1 message" đã dùng ở công thức băng thông trong bài Topic — message càng nhiều trường lồng nhau, càng tốn băng thông khi publish tần suất cao.
Thông số
Kích thước byte của các kiểu dữ liệu nguyên thuỷ trong .msg — dùng đúng kiểu vừa đủ giúp tiết kiệm băng thông khi thiết kế message tuỳ chỉnh:
| Kiểu | Kích thước |
|---|---|
int8 / uint8 / bool | 1 byte |
int16 / uint16 | 2 byte |
int32 / uint32 / float32 | 4 byte |
int64 / uint64 / float64 | 8 byte |
string | Độ dài thay đổi (4 byte lưu độ dài + nội dung thực tế) |
Register / Bit / Byte
Khi một message ROS2 được gửi qua mạng, nó không truyền dạng object ngôn ngữ (Python object, C++ struct) trực tiếp — nó được tuần tự hoá (serialize) thành một chuỗi byte theo chuẩn CDR (Common Data Representation), chuẩn mã hoá dữ liệu do DDS quy định. Giống hệt padding trong struct C đã nói ở chuyên mục Lập trình nhúng, CDR cũng áp dụng quy tắc căn chỉnh bộ nhớ (alignment) — một trường float64 (8 byte) trong message luôn bắt đầu ở offset chia hết cho 8 trong chuỗi byte tuần tự hoá, có thể chèn thêm byte đệm nếu trường trước đó không đủ căn chỉnh, hoàn toàn tương tự nguyên lý padding struct đã học.
Ứng dụng thực tế
Khi thiết kế message tuỳ chỉnh cho một robot cụ thể, gộp các trường liên quan chặt chẽ vào 1 message thay vì tách thành nhiều topic riêng lẻ thường hợp lý hơn — VD: 1 message BatteryStatus.msg gồm voltage, current, percentage, temperature publish chung 1 topic, thay vì 4 topic riêng biệt cho từng giá trị — giúp bên nhận luôn có đủ 4 giá trị đồng bộ tại cùng 1 thời điểm (cùng 1 message), tránh tình huống đọc voltage ở thời điểm này và current ở thời điểm khác rồi tính toán sai lệch do lệch thời gian giữa 2 lần đọc từ 2 topic riêng.
Ví dụ thực tế
$ ros2 interface show geometry_msgs/msg/Twist
Output:
# This expresses velocity in free space broken into its linear and angular parts.
Vector3 linear
float64 x
float64 y
float64 z
Vector3 angular
float64 x
float64 y
float64 z
$ ros2 topic type /cmd_vel
Output:
geometry_msgs/msg/Twist
ros2 interface show là cách tra cứu nhanh nhất cấu trúc đầy đủ của bất kỳ message nào (kể cả message lồng nhau nhiều tầng) mà không cần tìm và mở file .msg gốc trong source code — hữu ích khi làm việc với package của người khác không quen thuộc.
Lỗi thường gặp
- Không rebuild sau khi sửa file
.msg— code Python/C++ dùng class message cũ (đã sinh trước khi sửa) vẫn chạy được nhưng thiếu/thừa trường so với định nghĩa mới — luôncolcon buildlại package chứa message sau mỗi lần sửa.msg. - Đặt tên trường trùng từ khoá dành riêng của ngôn ngữ — VD: đặt tên trường là
classhoặcimport(từ khoá Python) gây lỗi sinh code hoặc xung đột cú pháp khi dùng trong code Python. - Chọn kiểu số quá hẹp cho khoảng giá trị thực tế — giống lỗi tràn số đã nói ở chuyên mục Lập trình nhúng, dùng
int8cho giá trị có thể vượt quá 127 (VD: phần trăm pin tính sai đơn vị) gây tràn số âm thầm. - Phụ thuộc vòng tròn giữa 2 package message tuỳ chỉnh — package A định nghĩa message dùng kiểu từ package B, package B lại dùng kiểu từ package A —
colconkhông thể xác định thứ tự build hợp lệ, cần tái cấu trúc để loại bỏ phụ thuộc vòng tròn.
Kết luận
Message là "hợp đồng" định nghĩa hình dạng dữ liệu trao đổi giữa các node, được sinh tự động thành class cho từng ngôn ngữ từ 1 nguồn .msg duy nhất — đảm bảo tính nhất quán bất kể ngôn ngữ lập trình. Hiểu cách CDR tuần tự hoá message thành byte giúp lý giải chính xác chi phí băng thông đã tính ở bài Topic. Bài tiếp theo trong chuyên mục đi sâu hơn vào Service — cơ chế request-response, và cách nó được cài đặt từ Message + Topic bên dưới.