9. วิธีสร้างและเรียกใช้งาน Service

หลังจากที่เราได้เรียนรู้การสื่อสารผ่าน Topic ซึ่งเป็น Publish-Subscribe Model ใน ROS 2 แล้ว บทนี้จะกล่าวถึง Service ซึ่งเป็นอีกหนึ่งวิธีในการสื่อสารระหว่างโหนดที่มีโครงสร้างแบบ Request-Response Model หรือ การร้องขอ และตอบกลับ

Service มีลักษณะคล้ายกับฟังก์ชันที่โหนดหนึ่งเรียกใช้งานอีกโหนดหนึ่ง โดยต้องมี Service Server สำหรับรับคำขอ และ Service Client สำหรับส่งคำขอ

9.1. ความเข้าใจเกี่ยวกับ Service ใน ROS 2

Service ใน ROS 2 ทำงานแบบ synchronous (ซิงโครนัส) หรือ asynchronous (แอสิงโครนัส) ก็ได้ ซึ่งประกอบไปด้วยสองส่วนหลัก

  • Service Server → ทำหน้าที่รอฟังคำขอจาก Client และตอบกลับค่าผลลัพธ์

  • Service Client → ส่งคำขอไปยัง Server และรอรับค่าผลลัพธ์

9.2. สร้าง Service Server

สร้างไฟล์ Python สำหรับ Server (สร้างในโฟล์เดอร์ my_packet)

  • service_server.py

_images/9.2.1.png

เขียนโค้ดลงในไฟล์ service_server.py จากนั้นกด Save

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts  

class AddTwoIntsServer(Node):
    def __init__(self):
        super().__init__('add_two_ints_server')  
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
        self.get_logger().info('Service Server Ready!')

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b  
        self.get_logger().info(f'Received request: {request.a} + {request.b} = {response.sum}')
        return response

def main():
    rclpy.init()
    node = AddTwoIntsServer()
    rclpy.spin(node)  
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

คำอธิบายโค้ด

  1. นำเข้าโมดูลที่จำเป็น

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
  • rclpy: ใช้ในการทำงานกับ ROS 2 ในภาษา Python

  • Node: ใช้ในการสร้าง Node ใน ROS 2

  • AddTwoInts: นำเข้า Service Type ที่มีการกำหนดไว้ใน example_interfaces เพื่อใช้ในการสร้าง Service สำหรับการบวกสองตัวเลข

  1. การสร้าง AddTwoIntsServer คลาส

class AddTwoIntsServer(Node):
    def __init__(self):
        super().__init__('add_two_ints_server')  
        self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
        self.get_logger().info('Service Server Ready!')
  • คลาส AddTwoIntsServer สืบทอดมาจาก Node และสร้าง Service ที่ชื่อ add_two_ints

  • create_service(): สร้าง Service และกำหนด callback function ที่จะรับการร้องขอจาก Client

  • เมื่อเริ่มต้น Node, จะพิมพ์ข้อความ 'Service Server Ready!' ใน log

  1. การสร้าง Callback Function

    def add_two_ints_callback(self, request, response):
        response.sum = request.a + request.b  
        self.get_logger().info(f'Received request: {request.a} + {request.b} = {response.sum}')
        return response
  • add_two_ints_callback: เป็นฟังก์ชันที่รับคำขอจาก Client ที่ส่ง a และ b ไปยัง Server

  • ทำการบวกตัวเลขทั้งสอง (request.a + request.b) และเก็บผลลัพธ์ใน response.sum

  • พิมพ์ข้อมูลการบวกเลขใน log

  1. การเริ่มต้น Node และการรักษาการทำงาน

def main():
    rclpy.init()
    node = AddTwoIntsServer()
    rclpy.spin(node)  
    node.destroy_node()
    rclpy.shutdown()
  • rclpy.init(): เริ่มต้น ROS 2

  • สร้าง AddTwoIntsServer Node

  • ใช้ rclpy.spin(node) เพื่อให้ Node ทำงานและรับการร้องขอจาก Client

  • หลังจากการทำงานเสร็จสิ้น, จะ destroy_node และปิดการทำงานของ ROS 2 ด้วย rclpy.shutdown()

  1. การเรียกใช้ main()

if __name__ == '__main__':
    main()
  • ถ้าไฟล์นี้ถูกเรียกใช้โดยตรง (ไม่ใช่การนำเข้าจากไฟล์อื่น), จะเรียกใช้ main() เพื่อเริ่มต้น Service Server และเริ่มให้บริการ

สรุป โค้ดนี้เป็นตัวอย่าง Service Server ใน ROS 2 ที่ใช้ example_interfaces/srv/AddTwoInts (Service มาตรฐานที่มีอยู่แล้วในระบบ) เพื่อรับคำขอจาก Client และส่งผลรวมกลับไป

9.3. สร้าง Service Client

สร้างไฟล์ Python สำหรับ Client

  • service_client.py

_images/9.3.1.png

เขียนโค้ดลงในไฟล์ service_client.py จากนั้นกด Save

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts  

class AddTwoIntsClient(Node):
    def __init__(self):
        super().__init__('add_two_ints_client')
        self.client = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Waiting for Service...')
        self.request = AddTwoInts.Request()

    def send_request(self, a, b):
        self.request.a = a
        self.request.b = b
        future = self.client.call_async(self.request)
        rclpy.spin_until_future_complete(self, future)
        return future.result().sum

def main():
    rclpy.init()
    node = AddTwoIntsClient()
    result = node.send_request(5, 10)
    node.get_logger().info(f'Result: {result}')
    node.destroy_node()
    rclpy.shutdown()

if __name__ == '__main__':
    main()

คำอธิบายโค้ด

  1. นำเข้าโมดูลที่จำเป็น

import rclpy
from rclpy.node import Node
from example_interfaces.srv import AddTwoInts
  • rclpy: ใช้ในการทำงานกับ ROS 2 ในภาษา Python

  • Node: ใช้ในการสร้าง Node ใน ROS 2

  • AddTwoInts: นำเข้า Service Type ที่มีการกำหนดไว้ใน example_interfaces เพื่อใช้ในการสร้าง Service สำหรับการบวกสองตัวเลข

  1. การสร้าง AddTwoIntsServer คลาส

class AddTwoIntsClient(Node):
    def __init__(self):
        super().__init__('add_two_ints_client')
        self.client = self.create_client(AddTwoInts, 'add_two_ints')
        while not self.client.wait_for_service(timeout_sec=1.0):
            self.get_logger().info('Waiting for Service...')
        self.request = AddTwoInts.Request()
  • คลาส AddTwoIntsServer สืบทอดมาจาก Node และสร้าง Service ที่ชื่อ add_two_ints

  • ใช้ create_client() เพื่อสร้าง Client ที่เชื่อมต่อกับ Service และรอจนกว่า Service Server จะพร้อม

  • wait_for_service(timeout_sec=1.0): รอจนกว่า Service จะพร้อมในเวลาไม่เกิน 1 วินาที

  1. การส่งคำขอ (Request)

    def send_request(self, a, b):
        self.request.a = a
        self.request.b = b
        future = self.client.call_async(self.request)
        rclpy.spin_until_future_complete(self, future)
        return future.result().sum
  • send_request: ฟังก์ชันที่รับค่าตัวเลข a และ b จาก Client และส่งไปยัง Service Server

  • call_async(self.request): เรียก Service แบบ asynchronous (ไม่บล็อกการทำงาน)

  • rclpy.spin_until_future_complete(self, future): รอจนกว่าผลลัพธ์ของคำขอจะได้รับจาก Service

  • future.result().sum: รับผลลัพธ์ของการบวกตัวเลขจาก Service Server และส่งค่าผลลัพธ์กลับไป

  1. การเริ่มต้น Node และส่งคำขอ

def main():
    rclpy.init()
    node = AddTwoIntsClient()
    result = node.send_request(5, 10)
    node.get_logger().info(f'Result: {result}')
    node.destroy_node()
    rclpy.shutdown()
  • rclpy.init(): เริ่มต้น ROS 2

  • สร้าง AddTwoIntsClient Node

  • เรียกใช้ send_request(5, 10) เพื่อส่งตัวเลข 5 และ 10 ไปยัง Service Server

  • แสดงผลลัพธ์ของการบวกใน log โดยใช้ node.get_logger().info

  • node.destroy_node(): ลบ Node เมื่อเสร็จสิ้นการทำงาน

  • rclpy.shutdown(): ปิดการทำงานของ ROS 2

  1. การเรียกใช้ main()

if __name__ == '__main__':
    main()
  • ถ้าไฟล์นี้ถูกเรียกใช้โดยตรง (ไม่ใช่การนำเข้าจากไฟล์อื่น), จะเรียกใช้ main() เพื่อเริ่มต้น Client และทำการเรียก Service

การทำงาน

  • Service Client นี้จะส่งคำขอไปยัง Service Server เพื่อบวกเลขสองตัว (เช่น 5 และ 10)

  • Service Server จะตอบกลับผลลัพธ์ของการบวก (15) ไปยัง Client

  • Client จะรับผลลัพธ์และแสดงใน log

สรุป โค้ดนี้เป็นตัวอย่างของ Service Client ใน ROS 2 ที่ใช้ example_interfaces/srv/AddTwoInts (Service มาตรฐานที่มีอยู่แล้วในระบบ) เพื่อบวกตัวเลขสองจำนวนและรับผลลัพธ์จาก Service Server

9.4. ทดสอบการทำงานของ Service

ตั้งค่า setup.py ใน VS Code เพิ่ม

'service_server = my_package.service_server:main',
'service_client = my_package.service_client:main',
_images/9.4.1.png

เปิด Terminal คอมไพล์

cd ~/ros2_ws 
colcon build

เปิด Terminal ใหม่ รัน Service Server

ros2 run my_package service_server 
  • รอให้ Server แสดงข้อความ Service Server Ready! ก่อนเปิด Client

เปิด Terminal ใหม่ รัน Service Client เพื่อทดสอบ

ros2 run my_package service_client
_images/9.4.2.png

ผลการทำงาน

  1. Service Server (ทางซ้าย)

  • เมื่อ Service Server เริ่มทำงาน, จะแสดงข้อความใน log ว่า "Service Server Ready!" ซึ่งบ่งบอกว่า Server พร้อมที่จะรับ คำสั่ง จาก Client

  • เมื่อ Service Client ส่ง คำสั่ง ที่ประกอบด้วยตัวเลขสองตัว (เช่น 5 และ 10) ไปยัง Service Server, Service Server จะทำการบวกตัวเลขทั้งสองและส่งผลลัพธ์กลับไปยัง Client

  • Service Server จะแสดงข้อความใน log ว่า "Received command: 5 + 10 = 15" เพื่อยืนยันว่าได้รับ คำสั่ง และได้ทำการประมวลผลคำขอแล้ว

  1. Service Client (ทางขวา)

  • Service Client ส่ง คำสั่ง ไปยัง Service Server เพื่อบวกตัวเลข 5 และ 10

  • หลังจากที่ Service Server ประมวลผลและส่งผลลัพธ์กลับมา, Service Client จะได้รับผลลัพธ์ 15 และแสดงข้อความใน log ว่า "Result: 15" ซึ่งบ่งชี้ว่า Client ได้รับผลลัพธ์จาก Service Server และแสดงผลลัพธ์นี้ใน log ของ Client

สามารถใช้คำสั่ง ros2 service ใน Terminal เพื่อเรียกใช้งาน Service ได้ทันที ซึ่งเป็นอีกทางเลือกหนึ่งในการทำงานกับ Service ใน ROS 2 ผ่าน Command Line

ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 7, b: 8}"

อธิบายคำสั่ง

ros2 service call
  • ใช้สำหรับเรียก Service ใน ROS 2 จาก Terminal โดยทำงานร่วมกับ Service Server ที่มีชื่อเฉพาะ

/add_two_ints
  • ชื่อของ Service ที่ต้องการเรียกใช้งาน ในที่นี้คือ Service ที่ให้บริการการบวกตัวเลขสองตัว (add_two_ints)

example_interfaces/srv/AddTwoInts
  • ชนิดของ Service ที่ใช้ ในที่นี้คือ AddTwoInts ซึ่งกำหนดไว้ใน example_interfaces โดย Service นี้รับค่าตัวเลขสองตัวและคืนค่าผลลัพธ์จากการบวก

"{a: 7, b: 8}"
  • ค่าพารามิเตอร์ที่ส่งไปยัง Service ได้แก่ a = 7 และ b = 8 ซึ่งเป็นค่าที่ Client ส่งให้ Service Server เพื่อให้ทำการบวกตัวเลขสองตัวนี้

สามารถทดลองใช้เครื่องมือ RQT ในการเรียก Service

_images/9.4.3.png _images/9.4.4.png

สรุป

Service Server พร้อมรับ คำสั่ง จาก Client เมื่อ Client ส่ง คำสั่ง (7, 8), Server จะประมวลผล และคำนวณผลลัพธ์ (7 + 8 = 15) Server แสดงข้อความว่า "Received command: 7 + 8 = 15" เพื่อบ่งบอกว่าการประมวลผลเสร็จสิ้น และส่งผลลัพธ์กลับไปยัง Client

_images/9.4.5.png

โจทย์

สร้าง Service ใน ROS 2 ที่ให้ Client สามารถส่งตัวเลขสองตัว (a และ b) เพื่อให้ Service Server ทำการ คูณ ตัวเลขทั้งสองแล้วส่งผลลัพธ์กลับไปยัง Client โดยใช้โปรแกรมที่เขียนด้วยภาษา Python ดังนี้

รายละเอียด

  1. Service Server

  • รับคำสั่งจาก Client โดยมีสองตัวเลข a และ b

  • ทำการคูณตัวเลขทั้งสอง a * b

  • ส่งผลลัพธ์ของการคูณกลับไปยัง Client

  1. Service Client

  • ส่งคำสั่งไปยัง Service Server โดยมีตัวเลขสองตัว a และ b

  • รอรับผลลัพธ์จาก Service Server

  • แสดงผลลัพธ์ที่ได้รับจาก Service Server

สรุปองค์ประกอบและรูปแบบการทำงานของ Service

องค์ประกอบของ ROS2 Service

องค์ประกอบ

คำอธิบาย

Service Server

โหนดที่รับ คำสั่ง และดำเนินการตามที่ได้รับ เช่น คำนวณผลลัพธ์ แล้วส่งกลับให้ Client

Service Client

โหนดที่ส่ง คำสั่ง ไปยัง Server เพื่อให้ดำเนินการบ้างอย่างและรอรับผลลัพธ์

Service Type (Message Type)

ข้อความที่ใช้สื่อสารระหว่าง Client และ Server มีโครงสร้าง {Request, Response}

รูปแบบการทำงานของ Service

  • Service Client ส่ง คำสั่ง ไปยัง Service Server

  • Service Server รับ คำสั่ง ประมวลผลและส่งผลลัพธ์ (Response) กลับ

  • Service Client ได้รับผลลัพธ์และสามารถดำเนินการต่อไปได้

Warning

เปรียบเทียบ

Topic: เหมือนการกระจายข่าวสารออกไป โดยไม่จำเป็นต้องมีผู้รับฟังทุกครั้ง Service: เหมือนการโทรศัพท์สอบถามข้อมูล มีการโต้ตอบแบบหนึ่งต่อหนึ่ง