11. Custom Message
ในบทก่อนหน้า ผู้เรียนได้ทำความเข้าใจการสื่อสารระหว่าง Node ใน ROS 2 ผ่านกลไกของ Publisher/Subscriber, Service, และ Action ซึ่งทั้งหมดล้วนใช้ “ข้อความ” (Message) ในการแลกเปลี่ยนข้อมูลระหว่างกัน โดยข้อความเหล่านี้ส่วนใหญ่จะมาจาก ชนิดข้อมูลมาตรฐาน (Standard Message Types) เช่น std_msgs/msg/String, geometry_msgs/msg/Twist, หรือ sensor_msgs/msg/LaserScan อย่างไรก็ตาม ในการพัฒนาระบบหุ่นยนต์จริง มักมีกรณีที่ต้องส่งข้อมูลเฉพาะทาง เช่น สถานะของแขนกล, ระดับพลังงานแบตเตอรี่,หรือค่าที่มาจากเซนเซอร์หลายตัวพร้อมกัน ซึ่งข้อความมาตรฐานใน ROS 2 ไม่สามารถรองรับได้ครบทุกกรณีดังนั้นนักพัฒนาจึงต้องสร้าง Custom Message ขึ้นมาเอง เพื่อกำหนดโครงสร้างข้อมูลที่เหมาะสมกับงานของตนเอ
11.1. สร้าง ไฟล์ .msg
โดยปกติ แพ็กเกจแบบ ament_python ไม่รองรับการสร้าง .msg, .srv, .action โดยตรง เพราะขั้นตอน generate โค้ดของ ROS 2 (ผ่าน rosidl_*) ใช้ pipeline ฝั่ง CMake เป็นหลัก จึงต้องทำ แพ็กเกจอินเทอร์เฟซแยก ที่เป็น ament_cmake แล้วให้แพ็กเกจโค้ด Python (ament_python) ไปพึ่งพาใช้อีกที
เปิด Terminal ขึ้นมา เพื่อสร้างแพ็กเกจแบบ ament_cmake
cd ~/ros2_ws/src
ros2 pkg create --build-type ament_cmake --license Apache-2.0 custom_tutorials
cd ~/ros2_ws
colcon build
เมื่อสร้างแพ็กเกจขึ้นมาแล้วจะได้โฟล์เดอร์ตามภาพด้านล่าง
เปิด VS Code เลือก File > Open Folder
และเลือก โฟลเดอร์ src จากนั้นกด Open
จะปรากฎ 2 โฟลเดอร์แพ็คเกจภายใน
สร้างโฟล์เดอร์ msg ภายใน custom_tutorials
สร้างไฟล์ RobotStatus.msg ภายใน msg
จากนั้นเขียนเนื้อหาในไฟล์ RobotStatus.msg ดังนี้
int32 id
float32 battery
string status
เปิดไฟล์ CMakeLists.txt
ทำให้โค้ด CMakeLists.txt เป็นสีใน VS Code
ค้นหาและติดตั้ง Extension CMake Tools (จาก Microsoft)
เพิ่มเนื้อหานี้ลงในไฟล์ CMakeLists.txt ดังนี้
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/RobotStatus.msg"
)
เปิดไฟล์ package.xml
เพิ่มเนื้อหานี้ลงในไฟล์ package.xml ดังนี้
<member_of_group>rosidl_interface_packages</member_of_group>
เปิด Terminal ขึ้นมา เพื่อทำการ คอมไพล์
cd ~/ros2_ws
colcon build
ตรวจสอบว่า message ถูกสร้างสำเร็จ
ros2 interface show custom_tutorials/msg/RobotStatus
11.2. ทดลองใช้ Custom Message ที่เราสร้างไว้ (RobotStatus.msg)
ในหัวข้อนี้เราจะสร้างโหนดตัวอย่างไว้ในแพ็กเกจ my_package (ชนิด ament_python) เพื่อทดสอบการสื่อสารแบบ Publisher/Subscriber โดยจะ อ้างอิงและใช้งานข้อความที่เราสร้างไว้ในแพ็กเกจ custom_tutorials (ชนิด ament_cmake) คือ custom_tutorials/msg/RobotStatus
สร้าง โหนด status_publisher.py และ status_subscriber.py ภายใน my_package
ตัวอย่างโค้ด status_publisher.py
import rclpy
from rclpy.node import Node
from custom_tutorials.msg import RobotStatus
class StatusPublisher(Node):
def __init__(self):
super().__init__('status_publisher')
self.publisher_ = self.create_publisher(RobotStatus, 'robot_status', 10)
self.timer = self.create_timer(1.0, self.publish_status)
def publish_status(self):
msg = RobotStatus()
msg.id = 1
msg.battery = 86.5
msg.status = "Running"
self.publisher_.publish(msg)
self.get_logger().info(f"Publishing: id={msg.id}, battery={msg.battery},status={msg.status}")
def main():
rclpy.init()
node = StatusPublisher()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
ตัวอย่างโค้ด status_subscriber.py
import rclpy
from rclpy.node import Node
from custom_tutorials.msg import RobotStatus
class StatusSubscriber(Node):
def __init__(self):
super().__init__('status_subscriber')
self.subscription = self.create_subscription(
RobotStatus,
'robot_status',
self.listener_callback,
10)
self.subscription
def listener_callback(self, msg):
self.get_logger().info(f"Received: id={msg.id}, battery={msg.battery}, status={msg.status}")
def main():
rclpy.init()
node = StatusSubscriber()
rclpy.spin(node)
node.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
ตั้งค่า setup.py
'status_publisher = my_package.status_publisher:main',
'status_subscriber = my_package.status_subscriber:main',
ตั้งค่า package.xml
<exec_depend>custom_tutorials</exec_depend>
เปิด Terminal ขึ้นมา ทำการ คอมไพล์
cd ~/ros2_ws
colcon build
ทดสอบการทำงานของโหนด status_publisher และ status_subscriber กับ RobotStatus Message
ros2 run my_package status_publisher
ros2 run my_package status_subscriber
สรุปสิ่งที่เพิ่มเติมในไฟล์ ให้รองรับและทำงานอย่างถูกต้อง
1. การสร้าง Message
เมื่อสร้างไฟล์ .msg ใหม่ เช่น RobotStatus.msg เราต้องปรับสองไฟล์หลักในแพ็กเกจที่สร้าง message คือ
(1) package.xml บอก ROS ว่าแพ็กเกจนี้เป็น “interface package” ที่ต้อง generate message:
(2) CMakeLists.txt เพิ่มคำสั่งให้ CMake เรียกตัวสร้างโค้ด (message generator):
บล็อกนี้คือสิ่งที่ทำให้ ROS 2 สร้างไฟล์ C++ และ Python ของ message อัตโนมัติหลัง build
2.การนำ Message ไปใช้งานใน Node (Python)
เมื่อ build เสร็จ ระบบจะสร้างโมดูล Python ขึ้นอัตโนมัติ จึงสามารถ import ได้เหมือนโมดูลทั่วไป:
โครงสร้างคือ from <ชื่อแพ็กเกจที่สร้าง message>.msg import <ชื่อไฟล์ msg>
3. การเชื่อมโยงแพ็กเกจที่นำไปใช้
ถ้าโค้ด Node อยู่ในอีกแพ็กเกจหนึ่ง (เช่น my_package) ต้องประกาศการพึ่งพาใน package.xml ด้วย:
<exec_depend>custom_tutorials</exec_depend> → เพื่อให้ระบบรู้ว่าต้องใช้ message จาก custom_tutorials <exec_depend>rclpy</exec_depend> → เพราะโหนดของเราใช้ ROS 2 Python API (rclpy)