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
_images/11.1.1.png

เมื่อสร้างแพ็กเกจขึ้นมาแล้วจะได้โฟล์เดอร์ตามภาพด้านล่าง

_images/11.1.2.png

เปิด VS Code เลือก File > Open Folder

_images/11.1.3.png

และเลือก โฟลเดอร์ src จากนั้นกด Open

_images/11.1.4.png

จะปรากฎ 2 โฟลเดอร์แพ็คเกจภายใน

_images/11.1.5.png

สร้างโฟล์เดอร์ msg ภายใน custom_tutorials

_images/11.1.6.png

สร้างไฟล์ RobotStatus.msg ภายใน msg

_images/11.1.7.png

จากนั้นเขียนเนื้อหาในไฟล์ RobotStatus.msg ดังนี้

int32 id
float32 battery
string status
_images/11.1.8.png

เปิดไฟล์ CMakeLists.txt

_images/11.1.9.png

ทำให้โค้ด CMakeLists.txt เป็นสีใน VS Code

  • ค้นหาและติดตั้ง Extension CMake Tools (จาก Microsoft)

_images/11.1.10.png _images/11.1.11.png

เพิ่มเนื้อหานี้ลงในไฟล์ CMakeLists.txt ดังนี้

find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
 "msg/RobotStatus.msg"
)
_images/11.1.12.png

เปิดไฟล์ package.xml

_images/11.1.13.png

เพิ่มเนื้อหานี้ลงในไฟล์ package.xml ดังนี้

<member_of_group>rosidl_interface_packages</member_of_group>
_images/11.1.14.png

เปิด Terminal ขึ้นมา เพื่อทำการ คอมไพล์

cd ~/ros2_ws
colcon build 
_images/11.1.15.png

ตรวจสอบว่า message ถูกสร้างสำเร็จ

ros2 interface show custom_tutorials/msg/RobotStatus
_images/11.1.16.png

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

_images/11.2.1.png

ตัวอย่างโค้ด 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',
_images/11.2.2.png

ตั้งค่า package.xml

<exec_depend>custom_tutorials</exec_depend>
_images/11.2.3.png

เปิด Terminal ขึ้นมา ทำการ คอมไพล์

cd ~/ros2_ws
colcon build
_images/11.2.4.png

ทดสอบการทำงานของโหนด status_publisher และ status_subscriber กับ RobotStatus Message

ros2 run my_package status_publisher
ros2 run my_package status_subscriber
_images/11.2.5.png

สรุปสิ่งที่เพิ่มเติมในไฟล์ ให้รองรับและทำงานอย่างถูกต้อง

1. การสร้าง Message

เมื่อสร้างไฟล์ .msg ใหม่ เช่น RobotStatus.msg เราต้องปรับสองไฟล์หลักในแพ็กเกจที่สร้าง message คือ

  • (1) package.xml บอก ROS ว่าแพ็กเกจนี้เป็น “interface package” ที่ต้อง generate message:

_images/11.2.6.png
  • (2) CMakeLists.txt เพิ่มคำสั่งให้ CMake เรียกตัวสร้างโค้ด (message generator):

_images/11.2.7.png

บล็อกนี้คือสิ่งที่ทำให้ ROS 2 สร้างไฟล์ C++ และ Python ของ message อัตโนมัติหลัง build

2.การนำ Message ไปใช้งานใน Node (Python)

เมื่อ build เสร็จ ระบบจะสร้างโมดูล Python ขึ้นอัตโนมัติ จึงสามารถ import ได้เหมือนโมดูลทั่วไป:

_images/11.2.8.png

โครงสร้างคือ from <ชื่อแพ็กเกจที่สร้าง message>.msg import <ชื่อไฟล์ msg>

3. การเชื่อมโยงแพ็กเกจที่นำไปใช้

ถ้าโค้ด Node อยู่ในอีกแพ็กเกจหนึ่ง (เช่น my_package) ต้องประกาศการพึ่งพาใน package.xml ด้วย:

_images/11.2.9.png

<exec_depend>custom_tutorials</exec_depend> → เพื่อให้ระบบรู้ว่าต้องใช้ message จาก custom_tutorials <exec_depend>rclpy</exec_depend> → เพราะโหนดของเราใช้ ROS 2 Python API (rclpy)