ตอนที่ 19: Robot State Publisher และ Joint State Publisher ผู้ปลุกเสกโมเดลให้มีชีวิต

1. 🎯 ตอนที่ 19: Robot State Publisher และ Joint State Publisher ผู้ปลุกเสกโมเดลให้มีชีวิต
สวัสดีครับน้องๆ ว่าที่วิศวกรหุ่นยนต์ทุกคน! ในตอนที่ผ่านๆ มา เราได้เรียนรู้วิธีการเขียน “พิมพ์เขียว” ของหุ่นยนต์ด้วย URDF และ Xacro กันไปแล้ว แต่พิมพ์เขียวก็ยังเป็นแค่ไฟล์ข้อความ XML นิ่งๆ ใช่ไหมครับ?
ถ้าน้องๆ เอา URDF ไปโหลดในระบบ ROS 2 ดื้อๆ หุ่นยนต์มันจะไม่รู้เลยว่าตอนนี้ล้อหมุนไปกี่องศาแล้ว หรือแขนกลยกขึ้นไปสูงแค่ไหน เพราะมันยังขาด “สมองส่วนที่ทำหน้าที่รับรู้โครงสร้างร่างกาย” วันนี้พี่จะพาไปรู้จักกับ Node พระเอก 2 ตัวที่จะมาประกอบร่างข้อมูล URDF เข้ากับองศาของมอเตอร์ แล้วแปลงออกมาเป็นระบบพิกัด TF Tree ให้ระบบทั้งระบบรับรู้ได้อย่างสมบูรณ์ครับ!
2. 📖 เปิดฉาก (The Hook)
น้องๆ ลองนึกภาพหุ่นยนต์เสิร์ฟอาหารที่มีแขนกลยืดหดได้ดูนะครับ สมมติว่าเซ็นเซอร์ LIDAR ของหุ่นยนต์มองเห็นกำแพงอยู่ห่างออกไป 1 เมตร การที่ระบบนำทางจะคำนวณหลบหลีกได้ มันต้องรู้ว่า LIDAR ติดอยู่ตรงไหนของตัวรถ และในขณะเดียวกัน ถ้าแขนกลกำลังยื่นออกไปหยิบแก้วน้ำ ระบบก็ต้องรู้ว่าปลายแขนกล (End-effector) ขยับออกห่างจากฐานรถไปกี่เซนติเมตรแล้ว
การจะมานั่งเขียนโค้ดตรีโกณมิติคำนวณ Forward Kinematics เองทุกครั้งที่มอเตอร์ขยับ คงเป็นฝันร้ายของโปรแกรมเมอร์แน่ๆ! วิศวกร ROS จึงได้สร้างเครื่องมือสำเร็จรูปที่ชื่อว่า robot_state_publisher และ joint_state_publisher ขึ้นมา เพื่อทำหน้าที่รับข้อมูล “มุมมอเตอร์” มาคำนวณร่วมกับ “พิมพ์เขียว URDF” แล้วกระจายบอกทุกคนในระบบผ่านระบบ TF แบบอัตโนมัติ! นี่แหละครับคือเวทมนตร์ของการลดภาระงานในสไตล์ ROS!
3. 🧠 แก่นวิชา (Core Concepts)
เพื่อให้เห็นภาพการทำงานร่วมกัน พี่ขอแยกอธิบายหน้าที่ของ Node แต่ละตัวเปรียบเทียบให้ฟังง่ายๆ แบบนี้ครับ:
joint_state_publisher(เส้นประสาทรับความรู้สึกจากกล้ามเนื้อ): Node ตัวนี้มีหน้าที่อ่านไฟล์ URDF เพื่อค้นหาว่ามี “ข้อต่อ (Joints)” ตัวไหนบ้างที่ขยับได้ (Non-fixed joints) จากนั้นมันจะทำการส่งข้อมูลสถานะของข้อต่อเหล่านั้น (เช่น ตำแหน่งองศา, ความเร็ว, และแรงบิด) ออกมาในรูปแบบข้อความsensor_msgs/JointStateบน Topic ที่ชื่อว่า/joint_states(Tips: สำหรับช่วงทดสอบ เรามักจะใช้joint_state_publisher_guiซึ่งจะมีหน้าต่าง Pop-up พร้อมสไลเดอร์ (Sliders) โผล่ขึ้นมาให้เราเอามือเลื่อนค่าองศาจำลองไปมาได้เลย)robot_state_publisher(สมองส่วนคำนวณตำแหน่งร่างกาย): นี่คือ Node ตัวท็อปของระบบ! มันต้องการอินพุต 2 อย่างคือ:- URDF (พิมพ์เขียว): รับเข้ามาผ่านพารามิเตอร์
robot_description /joint_states(มุมมอเตอร์): รับเข้ามาจากการ Subscribe Topic
เมื่อได้ข้อมูลครบ มันจะใช้ไลบรารี Kinematic Dynamics Library (KDL) ภายในตัวมัน คำนวณสมการ Forward Kinematics ทันที เพื่อหาว่าจากมุมมอเตอร์ปัจจุบัน ชิ้นส่วนแต่ละชิ้น (Link) จะมี 3D Poses (ตำแหน่งและทิศทางแบบ Quaternion) อยู่ตรงไหนในสเปซ 3 มิติ จากนั้นมันจะพ่นผลลัพธ์ออกไปที่ Topic
/tf(สำหรับชิ้นส่วนที่เคลื่อนไหว) และ/tf_static(สำหรับชิ้นส่วนที่ติดตายตัว)- URDF (พิมพ์เขียว): รับเข้ามาผ่านพารามิเตอร์
สรุปผังการไหลของข้อมูล (Data Flow) ก็คือ:
Hardware (หรือ GUI) -> พ่นลง /joint_states -> robot_state_publisher (อ่าน URDF) -> พ่นลง /tf และ /tf_static ให้ RViz หรือ Navigation เอาไปใช้ต่อครับ!

4. 💻 ร่ายมนต์โค้ดและคำสั่ง (Show me the Code)
เรามาดูวิธีร่ายมนต์ปลุก 2 Nodes นี้ผ่าน Terminal และ Launch File กันครับ!
1. การรันผ่าน Terminal (เพื่อทดสอบด่วน):
เปิด Terminal หน้าต่างแรก เพื่อรัน robot_state_publisher พร้อมโยนไฟล์ Xacro เข้าไป:
ros2 run robot_state_publisher robot_state_publisher --ros-args -p robot_description:="$(xacro /home/<user>/my_robot.urdf.xacro)"
# ถ้ารันสำเร็จ จะมี Log โชว์ขึ้นมาว่า:
# [robot_state_publisher]: got segment base_link
# [robot_state_publisher]: got segment right_wheelเปิด Terminal หน้าต่างที่สอง เพื่อรัน joint_state_publisher_gui เสกสไลเดอร์ขึ้นมาควบคุม:
ros2 run joint_state_publisher_gui joint_state_publisher_gui2. การประกอบร่างลง Python Launch File แบบมือโปร:
ในการใช้งานจริง เราจะจับทุกอย่างมัดรวมกันใน Launch File (display.launch.py) เพื่อให้เปิดระบบได้ในคำสั่งเดียวครับ:
import os
from launch import LaunchDescription
from launch_ros.actions import Node
from launch_ros.parameter_descriptions import ParameterValue
from launch.substitutions import Command
from ament_index_python.packages import get_package_share_path
def generate_launch_description():
# 1. หาพาทของไฟล์ URDF/Xacro ของเรา
urdf_path = os.path.join(get_package_share_path('my_robot_description'), 'urdf', 'my_robot.urdf.xacro')
# 2. แปลงไฟล์ Xacro เป็น String เก็บไว้ในตัวแปร Parameter
robot_description = ParameterValue(Command(['xacro ', urdf_path]), value_type=str)
# 3. กำหนด Node robot_state_publisher และยัดพารามิเตอร์เข้าไป
robot_state_publisher_node = Node(
package="robot_state_publisher",
executable="robot_state_publisher",
parameters=[{'robot_description': robot_description}]
)
# 4. กำหนด Node joint_state_publisher_gui (เพื่อใช้สไลเดอร์เทสโมเดล)
joint_state_publisher_gui_node = Node(
package="joint_state_publisher_gui",
executable="joint_state_publisher_gui"
)
# โยน Nodes เข้าไปใน LaunchDescription
return LaunchDescription([
robot_state_publisher_node,
joint_state_publisher_gui_node
])5. 🛡️ เคล็ดลับจากคัมภีร์ลับ (Under the Hood / Pro-Tips)
ในฐานะที่พี่ดีบักหุ่นยนต์มาเยอะ นี่คือจุดที่วิศวกรมือใหม่มักจะหัวหมุนที่สุดครับ:
- ลาก่อน GUI เมื่อใช้หุ่นยนต์จริง:
จำไว้นะครับว่า Node
joint_state_publisher_guiมีไว้สำหรับ จำลองและทดสอบ (Testing) เท่านั้น! เมื่อน้องๆ นำโค้ดไปรันบนหุ่นยนต์จริง หรือรันใน Simulator อย่าง Gazebo น้องๆ ห้าม สตาร์ท Node นี้เด็ดขาด เพราะฮาร์ดแวร์ไดรเวอร์ (เช่นros2_controlหรือตัวอ่าน Encoder ของล้อ) จะเป็นคนรับหน้าที่พ่นข้อมูลลง Topic/joint_statesแทน ถ้าน้องเปิด GUI ค้างไว้ ข้อมูลสไลเดอร์ของน้องจะไปตีกับข้อมูลฮาร์ดแวร์จริง จนล้อหุ่นยนต์ใน RViz หมุนกระตุกเป็นเจ้าเข้าเลยครับ! - RViz ขาวโพลน หรือ Error แดงเถือก (Missing TFs):
ถ้าน้องรัน
robot_state_publisherแล้ว แต่หุ่นยนต์ใน RViz แขนขาด ล้อหาย หรือมี Error ฟ้องว่า “No transform from [wheel_link] to [base_link]” สาเหตุยอดฮิต 99% คือ ไม่มีใครกำลังพ่นข้อมูลลง Topic/joint_statesครับ! เพราะถ้าrobot_state_publisherไม่ได้รับมุมมอเตอร์ มันก็จะไม่ยอมคำนวณ TF ของชิ้นส่วนที่ขยับได้ออกมาเลย วิธีเช็กง่ายๆ คือรันros2 topic echo /joint_statesดูครับว่ามีข้อมูลไหลมาไหม - ความแตกต่างของ
/tfและ/tf_static: เพื่อประหยัดทรัพยากรคอมพิวเตอร์robot_state_publisherจะแยกการส่งข้อมูล TF ออกเป็น 2 Topics ชิ้นส่วนที่ขยับไม่ได้ (Fixed joints) อย่างเซ็นเซอร์กล้อง จะถูกส่งผ่าน/tf_staticแค่ครั้งเดียวตอนเริ่มระบบ ส่วนชิ้นส่วนที่ขยับได้ (Revolute/Continuous) จะถูกพ่นผ่าน/tfด้วยความถี่สูงครับ
6. 🏁 บทสรุป (To be continued…)
การทำงานร่วมกันระหว่าง robot_state_publisher และ joint_state_publisher คือหัวใจหลักที่เปลี่ยนโมเดลกระดาษ (URDF) ให้กลายเป็นหุ่นยนต์ที่มีความรับรู้ใน 3D Space (TF Tree) มันเป็นกระบวนการมาตรฐานที่ระบบ ROS 2 ทุกระบบต้องใช้ ไม่ว่าจะเป็นหุ่นยนต์ดูดฝุ่นตัวเล็กๆ ไปจนถึงแขนกลอุตสาหกรรมในโรงงาน!
ตอนนี้เรามีโมเดล 3 มิติ มีระบบพิกัด TF และรู้วิธีขยับข้อต่อหุ่นยนต์ด้วยสไลเดอร์กันแล้ว! แต่เพื่อความสมจริง หุ่นยนต์ของเราควรจะต้องมีแรงโน้มถ่วง เดินชนกำแพงได้ และใช้เซ็นเซอร์อ่านค่าได้จริงๆ ในตอนต่อไป พี่จะพาน้องๆ โยนหุ่นยนต์ของเราเข้าสู่โลก Gazebo Simulator แบบเต็มตัว เตรียมบอกลา GUI Slider แล้วมาเขียนไดรเวอร์คุมมอเตอร์จำลองกันครับ!
ต้องการที่ปรึกษาด้านการออกแบบสถาปัตยกรรมหุ่นยนต์ (Robotics) และระบบ Automation ให้กับองค์กรของคุณ? ทีมงาน WP Solution พร้อมให้บริการออกแบบและพัฒนาซอฟต์แวร์แบบครบวงจร ดูรายละเอียดบริการของเราได้ที่: www.wpsolution2017.com หรือพูดคุยปรึกษาเบื้องต้นได้ที่ Line: wisit.p