การพัฒนาหุ่นยนต์เคลื่อนที่ด้วย ROS2 ตอนที่ 1

ตัวอย่างนี้จะเป็นการสร้างหุ่นยนต์เคลื่อนที่ ที่สามารถควบคุมผ่าน wifi ได้ ซึ่งจะเป็นระบบพื้นฐานของหุ่นยนต์เคลื่อนที่แบบอัตโนมัติในพื้นที่ปิด (indoor) ที่มีความสามารถในการสร้างแผนที่และเคลื่อนที่ไปยังตำแหน่งที่ต้องการพร้อมหลบหลีกสิ่งกีดขวางได้โดยอัตโนมัติ

การทำงานจะแยกเป็น 2 ส่วนคือ

  1. PC ซึ่งทำหน้าที่เป็นตัวรับข้อมูลจากเซนเซอร์ต่างๆบนหุ่นยนต์มาสร้างและแสดงผลแผนที่ รวมทั้งระบุตำแหน่งปัจจุบันของหุ่นยนต์ที่สอดคล้องกับตำแหน่งในแผนที่ โดยคอมพิวเตอร์จะติดตั้ง Ubuntu 22.04 พร้อมกับ ROS2 foxy
  2. หุ่นยนต์เคลื่อนที่แบบขับเคลื่อนด้วยล้อแยก (Differential Drive) ที่ติดตั้งมอเตอร์ DC จำนวน 2 ตัว และล้อเอนกประสงค์ (Caster Wheel) ที่ด้านหลัง มอเตอร์ DC ทั้งสองตัวติดตั้งตัวเข้ารหัส (Encoder) เพื่อช่วยในการติดตามความเร็วและระยะทางที่หุ่นยนต์เคลื่อนที่ มอเตอร์ถูกควบคุมด้วยโมดูลควบคุมมอเตอร์ L298N ซึ่งรับพลังงานจากแบตเตอรี่ลิเธียม 12 โวลต์  เซนเซอร์สำคัญอีกตัวหนึ่งคือ LiDAR ที่ติดตั้งอยู่ด้านบนของหุ่นยนต์ ซึ่งใช้สำหรับตรวจจับระยะทางและสภาพแวดล้อมรอบตัว ส่วนที่เป็น “สมอง” ของหุ่นยนต์ คือ Raspberry Pi 4 Model B ที่ใช้ในการประมวลผลและควบคุมการทำงานทั้งหมดของระบบ ที่จะทำงานร่วมกับ Arduino ที่ทำหน้าที่อ่านค่าตำแหน่งของมอเตอร์และควบคุมการหมุนของมอเตอร์ โดย Raspberry Pi จะติดตั้ง Ubuntu 22.04 พร้อมกับ ROS2 foxy ไว้เช่นกัน

เราจะเริ่มจากการสร้าง Node อย่างง่าย 2 node บน PC เพื่อทดสอบการรับส่งข้อมูลระหว่าง node คือ  Command_node และ Drive_node

ขั้นตอนที่ 1: การตั้งค่า ROS2 Workspace

  1. เปิด terminal และสร้าง Workspace Directory ด้วยคำสั่งดังนี้

 

mkdir -p ~/ros2_ws/src
cd ~/ros2_ws

เริ่มต้น Workspace ด้วยคำสั่ง   colcon build

  • Source Workspace หลังจาก build แล้ว ให้ source workspace:  source install/setup.bash

ขั้นตอนที่ 2: การสร้าง ROS2 Package

  1. ไปที่โฟลเดอร์ src :  cd ~/ros2_ws/src
  2. สร้าง Package : ros2 pkg create MobileRobot_py –build-type ament_python –dependencies rclpy std_msgs
  3. เข้าไปในโฟลเดอร์ Package: cd MobileRobot_py

ขั้นตอนที่ 3: การสร้าง Node

Command Node

  1. สร้างไฟล์ command_node.py และ drive_node.py
mkdir -p MobileRobot_py
touch MobileRobot_py/command_node.py
touch MobileRobot_py/drive_node.py
chmod +x MobileRobot_py/command_node.py
chmod +x MobileRobot_py/drive_node.py

เขียนโค้ดใน command_node.py

import rclpy
from rclpy.node import Node
from std_msgs.msg import String
from pynput import keyboard
import threading
class CommandNode(Node):
    def __init__(self):
        super().__init__(‘command_node’)
        self.publisher_ = self.create_publisher(String, ‘/drive_commands’, 10)
        self.get_logger().info(‘CommandNode is running. Press keys to send commands.’)
        self.listener = keyboard.Listener(on_press=self.on_press)
        self.listener.start()
    def on_press(self, key):
        try:
            if key.char == ‘i’:
                self.publish_command(‘Forward’)
            elif key.char == ‘j’:
                self.publish_command(‘TurnLeft’)
            elif key.char == ‘l’:
                self.publish_command(‘TurnRight’)
            elif key.char == ‘m’:
                self.publish_command(‘Backward’)
            elif key.char == ‘k’:
                self.publish_command(‘Stop’)
        except AttributeError:
            pass
    def publish_command(self, command):
        msg = String()
        msg.data = command
        self.publisher_.publish(msg)
        self.get_logger().info(f’Publishing: “{msg.data}”‘)
def main(args=None):
    rclpy.init(args=args)
    node = CommandNode()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        pass
    finally:
        node.destroy_node()
        rclpy.shutdown()
if __name__ == ‘__main__’:
    main()

2. Drive Node

  1. เขียนโค้ดใน drive_node.py
import rclpy
from rclpy.node import Node
from std_msgs.msg import String
class DriveNode(Node):
    def __init__(self):
        super().__init__(‘drive_node’)
        self.subscription = self.create_subscription(
            String,
            ‘/drive_commands’,
            self.listener_callback,
            10
        )
        self.get_logger().info(‘DriveNode is ready to receive commands.’)
    def listener_callback(self, msg):
        command = msg.data
        self.get_logger().info(f’Received command: “{command}”‘)
        if command == ‘Forward’:
            self.forward()
        elif command == ‘TurnLeft’:
            self.turn_left()
        elif command == ‘TurnRight’:
            self.turn_right()
        elif command == ‘Backward’:
            self.backward()
        elif command == ‘Stop’:
            self.stop()
        else:
            self.get_logger().warn(f’Unknown command: “{command}”‘)
    def forward(self):
        self.get_logger().info(‘Executing forward operation.’)
    def turn_left(self):
        self.get_logger().info(‘Executing turn left operation.’)
    def turn_right(self):
        self.get_logger().info(‘Executing turn right operation.’)
    def backward(self):
        self.get_logger().info(‘Executing backward operation.’)
    def stop(self):
        self.get_logger().info(‘Executing stop operation.’)
def main(args=None):
    rclpy.init(args=args)
    drive_node = DriveNode()
    try:
        rclpy.spin(drive_node)
    except KeyboardInterrupt:
        pass
    finally:
        drive_node.destroy_node()
        rclpy.shutdown()
if __name__ == ‘__main__’:
    main()

ขั้นตอนที่ 4: แก้ไขไฟล์ setup.py

  1. เปิดไฟล์ setup.py : nano setup.py
  2. แก้ไขเนื้อหาให้เป็นดังนี้:
from setuptools import setup
package_name = ‘MobileRobot_py’
setup(
    name=package_name,
    version=’0.0.0′,
    packages=[package_name],
    data_files=[
        (‘share/ament_index/resource_index/packages’,
            [‘resource/’ + package_name]),
        (‘share/’ + package_name, [‘package.xml’]),
    ],
    install_requires=[‘setuptools’],
    zip_safe=True,
    maintainer=’Your Name’,
    maintainer_email=’your.email@example.com’,
    description=’MobileRobot_py project with Command_node and Drive_node.’,
    license=’Apache License 2.0′,
    tests_require=[‘pytest’],
    entry_points={
        ‘console_scripts’: [
            ‘command_node = MobileRobot_py.command_node:main’,
            ‘drive_node = MobileRobot_py.drive_node:main’,
        ],
    },
)

ขั้นตอนที่ 5: Build Package

  1. กลับไปยัง Workspace Directory: cd ~/ros2_ws
  2. Build Package: colcon build
  3. Source Workspace : source install/setup.bash

ขั้นตอนที่ 6: รัน Node ใช้ 2 Terminal ในการรัน Node:

Terminal 1: รัน drive_node:  ros2 run MobileRobot_py drive_node

Terminal 2: รัน command_node: ros2 run MobileRobot_py command_node

Note:

  1. สามารถตรวจสอบการรับส่ง message ระหว่าง node ได้จากการใช้คำสั่ง ros2 topic list จะแสดงรายชื่อของ topic ที่มีการ publish ระหว่างแต่ละ node
  2. ถ้าต้องการดูรายละเอียดของ topic (เช่น ชนิดของข้อความที่ใช้) ทำได้โดยใช้คำสั่ง ros2 topic info <topic_name>
  3. ถ้าต้องการตรวจสอบข้อมูลในแต่ละ topic สามารถทำได้โดยใช้คำสั่ง ros2 topic echo <topic_name>

เมื่อรันเรียบร้อย คุณจะสามารถส่งคำสั่งผ่านแป้นพิมพ์เพื่อควบคุมหุ่นยนต์ได้ ดังรูป

สรุป

บทความนี้แสดงให้เห็นขั้นตอนการสร้าง ROS2 Package และ Node อย่างง่าย พร้อมตัวอย่างโค้ดที่ช่วยให้คุณเริ่มต้นใช้งาน ROS2 สำหรับการควบคุมหุ่นยนต์เคลื่อนที่ได้ทันที!  ในบทความถัดไป จะทดลองควบคุมหุ่นยนต์โดยการย้าย/สร้าง drive_node บนหุ่นยนต์และรอรับคำสั่งจาก command_node บน PC เพื่อควบคุมหุ่นยนต์ผ่านการกด keyboard

This entry was posted in Blog 101. Bookmark the permalink.

Leave a Reply

Your email address will not be published. Required fields are marked *