10. QoS — กับดักที่เจอกันมากที่สุด

Important

หัวข้อนี้ไม่ใช่เรื่องขั้นสูง แต่เป็นสาเหตุอันดับต้น ๆ ที่ทำให้คนเพิ่งเริ่ม เสียเวลาเป็นวันทั้งที่โค้ดถูกหมด

อาการคลาสสิก: ros2 topic list เห็นชื่อ topic · ros2 node list เห็น node ครบ · ไม่มี error สักบรรทัด · แต่ ไม่มีข้อมูลไหลถึงกันเลย

QoS คืออะไร

ข้อตกลงว่าจะส่งข้อมูลกัน "แบบไหน" — publisher เสนอเงื่อนไข subscriber ขอเงื่อนไข ถ้าเงื่อนไขไม่เข้ากัน ทั้งคู่จะไม่เชื่อมต่อกันเลย และไม่มีใครแจ้งอะไร

3 ข้อที่ทำให้พังบ่อยที่สุด

ข้อ

ตัวเลือก

ความหมาย

Reliability

RELIABLE

ส่งซ้ำจนกว่าจะถึง — ห้ามหาย

BEST_EFFORT

ส่งแล้วแล้วกัน หายได้ — เร็วกว่า

Durability

VOLATILE

ใครมาทีหลังไม่ได้ของเก่า

TRANSIENT_LOCAL

เก็บค่าล่าสุดไว้ให้คนมาทีหลัง

History

KEEP_LAST (depth)

เก็บล่าสุดกี่ชิ้น

กฎความเข้ากัน — จำข้อเดียวนี้พอ

Warning

🔴 ผู้ส่งต้องให้ "ไม่น้อยกว่า" ที่ผู้รับขอ

publisher ให้

subscriber ขอ

ผล

RELIABLE

RELIABLE

✅ ต่อได้

RELIABLE

BEST_EFFORT

✅ ต่อได้ (ให้มากกว่าที่ขอ)

BEST_EFFORT

RELIABLE

🔴 ไม่ต่อ — เงียบสนิท

BEST_EFFORT

BEST_EFFORT

✅ ต่อได้

แถวสีแดงคือกรณีที่เจอจริงเกือบทุกครั้ง เพราะ

  • เซ็นเซอร์ส่วนใหญ่ใช้ BEST_EFFORT (LiDAR · กล้อง · IMU) เพราะข้อมูลมาถี่ ตกไปบ้างไม่เป็นไร ค่าใหม่มาแทนใน 0.1 วินาที

  • ค่าเริ่มต้นของ create_subscription คือ RELIABLE

เขียน subscriber แบบตรงไปตรงมาแล้วไปฟัง /scan จึงไม่ได้อะไรเลย

วิธีตรวจ — คำสั่งเดียวจบ

ros2 topic info -v /scan
Type: sensor_msgs/msg/LaserScan

Publisher count: 1
Node name: lidar_node
QoS profile:
  Reliability: BEST_EFFORT      ← ผู้ส่งให้แค่นี้
  Durability: VOLATILE

Subscription count: 1
Node name: my_node
QoS profile:
  Reliability: RELIABLE          ← ผู้รับขอมากกว่า → ไม่ต่อกัน
  Durability: VOLATILE

Tip

-v คือหัวใจ — ถ้าไม่ใส่จะเห็นแค่จำนวน publisher/subscriber ซึ่งขึ้นเป็น 1 ทั้งคู่ ดูเหมือนทุกอย่างปกติ ทั้งที่ไม่ได้เชื่อมกัน

จำคำสั่งนี้ให้ขึ้นใจ มันคือคำสั่งที่ประหยัดเวลาได้มากที่สุดในคู่มือทั้งเล่ม

วิธีแก้

ใช้โปรไฟล์สำเร็จรูปที่ตรงกับชนิดข้อมูล

from rclpy.qos import qos_profile_sensor_data

self.sub = self.create_subscription(
    LaserScan, '/scan', self.on_scan,
    qos_profile_sensor_data)          # แทนที่จะใส่เลข 10

qos_profile_sensor_data คือ BEST_EFFORT + depth 5 ซึ่งตรงกับที่เซ็นเซอร์ส่ง

ถ้าต้องตั้งเอง

from rclpy.qos import QoSProfile, ReliabilityPolicy, DurabilityPolicy

qos = QoSProfile(
    depth=5,
    reliability=ReliabilityPolicy.BEST_EFFORT,
    durability=DurabilityPolicy.VOLATILE,
)

กับดักซ้อน — ros2 topic echo ก็ติดกฎเดียวกัน

ros2 topic echo ก็เป็น subscriber ตัวหนึ่ง จึงเจอปัญหาเดียวกันได้

ถ้า echo เงียบแต่ RViz เห็นข้อมูล ให้ลอง

ros2 topic echo /scan --qos-profile sensor_data

Danger

🔴 อย่าสรุปว่า "ไม่มีข้อมูล" จากการที่ echo เงียบเพียงอย่างเดียว

เคยมีคนไล่หาสาเหตุถึงขั้นถอดสาย LiDAR มาเปลี่ยน ทั้งที่ข้อมูลไหลปกติมาตลอด แค่เครื่องมือที่ใช้ดูมันเข้ากันไม่ได้

ตรวจด้วย ros2 topic info -v เสมอ ก่อนจะสรุปอะไรเกี่ยวกับฮาร์ดแวร์

TRANSIENT_LOCAL — ของที่ส่งครั้งเดียว

บาง topic ส่งข้อมูลครั้งเดียวแล้วเงียบ เช่น

topic

ทำไม

/map

แผนที่ส่งครั้งเดียวตอนโหลด

/tf_static

ตำแหน่งเซ็นเซอร์ไม่เคยเปลี่ยน

ถ้าใช้ VOLATILE node ที่เปิดทีหลังจะไม่มีวันได้แผนที่ เพราะพลาดตอนที่ส่งไปแล้ว

from rclpy.qos import QoSProfile, DurabilityPolicy

qos = QoSProfile(depth=1, durability=DurabilityPolicy.TRANSIENT_LOCAL)
self.sub = self.create_subscription(OccupancyGrid, '/map', self.on_map, qos)

Note

อาการของข้อนี้คือ "เปิดตามลำดับหนึ่งแล้วใช้ได้ อีกลำดับหนึ่งใช้ไม่ได้" ซึ่งดูเหมือนปัญหาจังหวะเวลา แต่จริง ๆ เป็นเรื่อง QoS

สรุปที่ควรจำ

อาการ "ดูปกติทุกอย่างแต่ไม่มีข้อมูล"  →  ros2 topic info -v ก่อนเสมอ

เซ็นเซอร์ (LiDAR · กล้อง · IMU)      →  qos_profile_sensor_data
แผนที่ · tf_static                    →  TRANSIENT_LOCAL
คำสั่งควบคุม (/cmd_vel)               →  ค่าเริ่มต้น (RELIABLE) ถูกแล้ว

Important

จบส่วนที่ 1 แล้ว ถึงตรงนี้ควรทำได้ 3 อย่าง

  1. อ่านออกว่าระบบมี node อะไร คุยกันทาง topic ไหน

  2. เขียน node ของตัวเองที่ส่งและรับข้อมูลได้

  3. หาสาเหตุเป็นเมื่อข้อมูลไม่ถึงกัน ซึ่งสำคัญที่สุดตอนต่ออุปกรณ์จริง

ส่วนที่ 2 จะเริ่มต่อของจริง เริ่มจากทำให้ล้อหมุนก่อน


ก่อนหน้า: 9. RViz · กลับหน้าแรก: ROS 2 สำหรับหุ่นยนต์เคลื่อนที่