ROS 2
UAT 308 ระบบอัตโนมัติและหุ่นยนต์
บทเรียน
เมื่อเรียนจบโมดูลนี้ ผู้เรียนจะสามารถ
- อธิบาย node, topic, service และ action ของ ROS 2 และบทบาทของ DDS
- เลือกนโยบาย QoS ด้าน history, depth และ reliability ให้เหมาะกับข้อมูลแต่ละชนิด
- วิเคราะห์เวลาหน่วงและการทิ้งข้อความเมื่อผู้รับประมวลผลช้ากว่าผู้ส่ง
- จับคู่ข้อความจากเซนเซอร์หลายตัวตามเวลาด้วยค่า slop ที่เหมาะสม
ทำไมต้องรู้
UAT 314 แนะนำแนวคิดของ ROS 2 และการเชื่อมกับ flight controller ผ่าน uXRCE-DDS และ MAVROS หน่วยความรู้เรื่อง ROS 2 และการเชื่อมต่อกับ flight stack ของคลังความรู้โดรนอธิบาย node topic service action และการจำลอง ส่วนหน่วยความรู้เรื่องจาก Embedded System สู่ AI Robot อธิบายการต่อกล้องและ sensor fusion บนฮาร์ดแวร์ Edge AI Macenski และคณะ (2022) สรุปว่า ROS 2 สร้างบน DDS เพื่อให้ตั้งคุณภาพการสื่อสาร (QoS) ได้ตามงาน ปัญหาที่พบบ่อยที่สุดในห้องปฏิบัติการไม่ใช่การเขียน node แต่คือ ข้อมูลมาช้า มาไม่ครบ หรือมาไม่ตรงเวลากัน โมดูลนี้จึงเน้นการตั้ง QoS และการจับคู่ข้อมูลตามเวลา
องค์ประกอบของ ROS 2
node คือโปรแกรมหนึ่งหน่วย topic ส่งข้อมูลต่อเนื่องแบบผู้ส่งหลายรายถึงผู้รับหลายราย เช่นภาพกล้องและค่า LiDAR service เป็นการถามตอบครั้งเดียว เช่นขอเปิดไฟส่องสว่าง action ใช้กับงานที่ใช้เวลานานและต้องรายงานความคืบหน้าหรือยกเลิกได้ เช่นสั่งรถวิ่งไปยังแถวแผงที่ 12 ใต้ ROS 2 คือ DDS ซึ่งค้นหากันเองในเครือข่ายโดยไม่ต้องมีตัวกลาง
นโยบาย QoS
เอกสาร ROS 2 อธิบายนโยบายหลักดังนี้ history แบบ keep last เก็บเฉพาะ N ตัวอย่างล่าสุดตาม depth ส่วน keep all เก็บทั้งหมดเท่าที่ทรัพยากรของมิดเดิลแวร์รองรับ reliability แบบ best effort พยายามส่งแต่อาจหายเมื่อเครือข่ายไม่ดี แบบ reliable รับประกันการส่งโดยอาจส่งซ้ำหลายครั้ง โปรไฟล์ค่าเริ่มต้นใช้ reliable, keep last, depth 10 ส่วนโปรไฟล์สำหรับข้อมูลเซนเซอร์ใช้ best effort, keep last, depth 5 เพราะสำหรับภาพกล้อง ข้อมูลล่าสุดสำคัญกว่าการได้ข้อมูลเก่าครบทุกภาพ ผู้ส่งกับผู้รับต้องตั้ง QoS ที่เข้ากันได้ มิฉะนั้นจะไม่เชื่อมต่อกัน
ตัวอย่างที่ 1 กล้อง 30 Hz กับตัวตรวจจุดร้อนที่ใช้เวลา 50 ms
กล้องความร้อนส่งภาพ 30 Hz เป็นเวลา 3 s ตัวตรวจจุดร้อนประมวลผลภาพละ 50 ms (ทำได้ 20 ภาพต่อวินาที) เปรียบเทียบ keep last depth 1 กับ depth 10 (แบบจำลองคิวอย่างง่าย ไม่รวมเวลาส่งผ่านเครือข่าย)
from collections import deque
PUB_HZ = 30 # กล้องส่งภาพ 30 Hz
PROC = 0.050 # ผู้รับใช้เวลาประมวลผลภาพละ 50 ms
DURATION = 3.0
def run(depth):
pubs = [k / PUB_HZ for k in range(int(DURATION * PUB_HZ))]
q = deque()
dropped, done, lat = 0, 0, []
t_free = 0.0
i = 0
while i < len(pubs) or q:
t_next = pubs[i] if i < len(pubs) else float("inf")
if q and t_free <= t_next:
stamp = q.popleft()
start = max(t_free, stamp)
lat.append(start + PROC - stamp)
t_free = start + PROC
done += 1
else:
if len(q) == depth:
q.popleft() # KEEP_LAST ทิ้งข้อความเก่าที่สุด
dropped += 1
q.append(t_next)
i += 1
lat.sort()
return done, dropped, sum(lat) / len(lat), lat[len(lat) // 2]
for depth in [1, 10]:
done, dropped, avg, med = run(depth)
print(f"depth={depth:>2} processed {done} dropped {dropped} "
f"mean latency {avg*1000:.0f} ms median {med*1000:.0f} ms")
depth= 1 processed 61 dropped 29 mean latency 66 ms median 67 ms
depth=10 processed 70 dropped 20 mean latency 334 ms median 367 ms
ผู้รับช้ากว่าผู้ส่งจึงต้องทิ้งภาพบางส่วนไม่ว่าจะตั้ง depth เท่าใด ความต่างคือ depth 10 ทำให้ภาพที่ถูกประมวลผลเป็นภาพเก่า ต้องรอในคิวราว s บวกเวลาประมวลผล เวลาหน่วงจึงเกือบ 0.4 s ส่วน depth 1 ได้ภาพล่าสุดเสมอ เวลาหน่วงราว 67 ms ถ้ารถวิ่ง 1 m/s ความต่างนี้เท่ากับตำแหน่งจุดร้อนคลาดราว 30 cm สำหรับงานควบคุมและตรวจจับแบบเวลาจริง คิวสั้นเหมาะกว่า ส่วนข้อมูลที่ต้องครบทุกชิ้น เช่นบันทึกเหตุการณ์ ควรใช้ reliable และคิวยาว
จับคู่ข้อมูลจากหลายเซนเซอร์ตามเวลา
การหลอมรวมกล้องกับ LiDAR ต้องใช้ข้อความที่วัดในเวลาใกล้กัน แพ็กเกจ message_filters ของ ROS มี ApproximateTimeSynchronizer ซึ่งรับพารามิเตอร์ slop เป็นวินาทีที่กำหนดว่าข้อความต่างเวลากันได้มากเท่าใดจึงจับคู่กันได้ ตัวอย่างต่อไปใช้วิธีอย่างง่ายคือหาภาพที่เวลาใกล้ที่สุดของแต่ละรอบ LiDAR ซึ่งง่ายกว่าอัลกอริทึมจริงของแพ็กเกจ แต่แสดงผลของ slop ได้ชัด
ตัวอย่างที่ 2 กล้อง 15 Hz กับ LiDAR 10 Hz
กล้องส่ง 15 Hz มีความแปรปรวนของเวลาเล็กน้อย LiDAR ส่ง 10 Hz เริ่มช้ากว่า 7 ms เป็นเวลา 2 s (ข้อมูลจำลอง)
import numpy as np
rng = np.random.default_rng(3)
cam = np.arange(0, 2.0, 1/15) + rng.normal(0, 0.004, 30) # กล้อง 15 Hz
lidar = np.arange(0.007, 2.0, 1/10) # LiDAR 10 Hz
def pair(slop):
pairs = []
for t in lidar:
j = np.argmin(np.abs(cam - t))
if abs(cam[j] - t) <= slop:
pairs.append((t, cam[j]))
return pairs
for slop in [0.005, 0.020, 0.040]:
p = pair(slop)
gaps = [abs(a - b) * 1000 for a, b in p]
print(f"slop {slop*1000:>2.0f} ms paired {len(p)}/{len(lidar)} "
f"largest gap {max(gaps):.1f} ms")
slop 5 ms paired 3/20 largest gap 3.2 ms
slop 20 ms paired 10/20 largest gap 15.1 ms
slop 40 ms paired 20/20 largest gap 34.1 ms
เพราะอัตรา 15 กับ 10 Hz ไม่ลงตัวกัน ครึ่งหนึ่งของรอบ LiDAR จึงมีภาพห่างเกิน 20 ms slop เล็กได้คู่ที่ตรงเวลาแต่น้อย slop ใหญ่ได้ครบแต่บางคู่ห่างกันถึง 34 ms ถ้ารถหมุนตัว 30 องศาต่อวินาที ความต่าง 34 ms เท่ากับทิศคลาดราว 1 องศา ทางแก้ที่ดีกว่าการขยาย slop คือทริกเกอร์เซนเซอร์ให้วัดพร้อมกัน หรือเลือกอัตราที่ลงตัวกัน
ปฏิบัติการประจำโมดูล
ปฏิบัติการ: ตั้ง QoS และวัดเวลาหน่วงจริง
- เขียน node ส่งภาพจากกล้องฝึกที่ 30 Hz และ node รับที่หน่วงเวลา 50 ms ต่อภาพ ใส่เวลาประทับทุกข้อความ
- วัดจำนวนภาพที่ประมวลผลและเวลาหน่วงเมื่อ depth เป็น 1, 5 และ 10 เทียบกับตัวอย่างที่ 1
- ลองตั้ง reliability ของผู้ส่งกับผู้รับไม่เข้ากัน สังเกตผล แล้วใช้
ros2 topic info --verboseตรวจ QoS - บันทึก bag ของกล้องกับ LiDAR แล้วจับคู่ด้วย ApproximateTimeSynchronizer ที่ slop ต่างกัน นับจำนวนคู่
- สรุปตาราง QoS ที่เลือกสำหรับทุก topic ของรถ UGV พร้อมเหตุผล
ข้อผิดพลาดที่พบบ่อย
ระวัง
- ใช้ reliable และคิวยาวกับภาพกล้อง จนภาพที่ประมวลผลเป็นภาพเก่า
- ตั้ง QoS ผู้ส่งกับผู้รับไม่เข้ากัน แล้วคิดว่าโค้ดเสีย
- ใช้เวลาที่รับข้อความแทนเวลาที่วัด ในการจับคู่
- ขยาย slop จนจับคู่ข้อมูลที่ต่างเวลามาก
- ไม่วัดเวลาหน่วงจริง ของทั้งสายตั้งแต่เซนเซอร์ถึงผลลัพธ์
สรุป
- ROS 2 สร้างบน DDS มี topic, service และ action สำหรับงานต่างชนิด
- QoS กำหนด history, depth และ reliability ให้เหมาะกับข้อมูลแต่ละแบบ
- ผู้รับที่ช้ากว่าผู้ส่งต้องทิ้งข้อมูล คิวยาวทำให้เวลาหน่วงเพิ่ม
- การจับคู่ตามเวลาต้องเลือก slop อย่างระวัง และดีที่สุดคือให้เซนเซอร์วัดพร้อมกัน
แบบฝึกตรวจความเข้าใจ
- งานใดควรใช้ action แทน service
- keep last depth 5 หมายความว่าอะไร
- ผู้ส่ง 30 Hz และคิว depth 10 เต็ม ภาพเก่าสุดในคิวรอมานานประมาณเท่าใด
- ทำไมโปรไฟล์ข้อมูลเซนเซอร์จึงใช้ best effort
- ถ้ารถหมุน 30 องศาต่อวินาที และคู่ข้อมูลต่างเวลากัน 20 ms ทิศคลาดเท่าใด
เฉลย
- งานที่ใช้เวลานานและต้องรายงานความคืบหน้าหรือยกเลิกได้ เช่นสั่งรถวิ่งไปยังตำแหน่ง
- เก็บเฉพาะ 5 ข้อความล่าสุด ข้อความเก่ากว่าถูกทิ้ง
- ประมาณ s
- ข้อมูลล่าสุดสำคัญกว่าการได้ข้อมูลเก่าครบ และการส่งซ้ำทำให้ช้า
สรุปสูตรสำคัญ
| อายุของข้อความเก่าสุดในคิวเต็ม | |
| เงื่อนไขจับคู่ตามเวลา |
แหล่งอ้างอิงหลัก
- Open Robotics. ROS 2 documentation. link
- Macenski, S., Foote, T., Gerkey, B., Lalancette, C., & Woodall, W. (2022). Robot Operating System 2: Design, architecture, and uses in the wild. Science Robotics, 7(66), eabm6074. link
- Open Robotics. Quality of Service settings. ROS 2 documentation (Jazzy). link
- Open Robotics. Approximate synchronizer (Python). message_filters documentation (ROS 2 Jazzy). link
อ่านเพิ่มเติม
ศึกษาหน่วยความรู้ที่กำหนดล่วงหน้า ดูสื่อประกอบ และทำ quiz ประจำโมดูล
ROS 2 และการเชื่อมต่อกับ flight stack
เนื้อหาเจาะลึก: จาก Embedded System สู่ AI Robot และ ROS
ในชั้นเรียน / ภาคสนาม
ปฏิบัติการในห้องแล็บหรือภาคสนามตามใบงาน พร้อม checklist ความปลอดภัย
หลักฐานการเรียนรู้: ใบงานที่ผ่านการตรวจและผล quiz