# วิธีสร้างและเรียกใช้งาน Service หลังจากที่เราได้เรียนรู้การสื่อสารผ่าน Topic ซึ่งเป็น Publish-Subscribe Model ใน ROS 2 แล้ว บทนี้จะกล่าวถึง Service ซึ่งเป็นอีกหนึ่งวิธีในการสื่อสารระหว่างโหนดที่มีโครงสร้างแบบ Request-Response Model หรือ การร้องขอ และตอบกลับ Service มีลักษณะคล้ายกับฟังก์ชันที่โหนดหนึ่งเรียกใช้งานอีกโหนดหนึ่ง โดยต้องมี Service Server สำหรับรับคำขอ และ Service Client สำหรับส่งคำขอ ## ความเข้าใจเกี่ยวกับ Service ใน ROS 2 Service ใน ROS 2 ทำงานแบบ synchronous (ซิงโครนัส) หรือ asynchronous (แอสิงโครนัส) ก็ได้ ซึ่งประกอบไปด้วยสองส่วนหลัก * Service Server → ทำหน้าที่รอฟังคำขอจาก Client และตอบกลับค่าผลลัพธ์ * Service Client → ส่งคำขอไปยัง Server และรอรับค่าผลลัพธ์ ## สร้าง Service Server สร้างไฟล์ Python สำหรับ Server (สร้างในโฟล์เดอร์ my_packet) * service_server.py ```{image} images/c9/9.2.1.png :width: 80% :align: center ``` เขียนโค้ดลงในไฟล์ service_server.py จากนั้นกด Save ```{code-block} python 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. นำเข้าโมดูลที่จำเป็น ```{code-block} python 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 สำหรับการบวกสองตัวเลข 2. การสร้าง AddTwoIntsServer คลาส ```{code-block} python 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 3. การสร้าง Callback Function ```{code-block} python 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 4. การเริ่มต้น Node และการรักษาการทำงาน ```{code-block} python 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() 5. การเรียกใช้ main() ```{code-block} python if __name__ == '__main__': main() ``` * ถ้าไฟล์นี้ถูกเรียกใช้โดยตรง (ไม่ใช่การนำเข้าจากไฟล์อื่น), จะเรียกใช้ main() เพื่อเริ่มต้น Service Server และเริ่มให้บริการ **สรุป** โค้ดนี้เป็นตัวอย่าง Service Server ใน ROS 2 ที่ใช้ example_interfaces/srv/AddTwoInts (Service มาตรฐานที่มีอยู่แล้วในระบบ) เพื่อรับคำขอจาก Client และส่งผลรวมกลับไป ## สร้าง Service Client สร้างไฟล์ Python สำหรับ Client * service_client.py ```{image} images/c9/9.3.1.png :width: 80% :align: center ``` เขียนโค้ดลงในไฟล์ service_client.py จากนั้นกด Save ```{code-block} python 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. นำเข้าโมดูลที่จำเป็น ```{code-block} python 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 สำหรับการบวกสองตัวเลข 2. การสร้าง AddTwoIntsServer คลาส ```{code-block} python 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 วินาที 3. การส่งคำขอ (Request) ```{code-block} python 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 และส่งค่าผลลัพธ์กลับไป 4. การเริ่มต้น Node และส่งคำขอ ```{code-block} python 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 5. การเรียกใช้ main() ```{code-block} python 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 ## ทดสอบการทำงานของ Service ตั้งค่า setup.py ใน VS Code เพิ่ม ```{code-block} bash 'service_server = my_package.service_server:main', 'service_client = my_package.service_client:main', ``` ```{image} images/c9/9.4.1.png :width: 80% :align: center ``` เปิด Terminal คอมไพล์ ```{code-block} bash cd ~/ros2_ws colcon build ``` เปิด Terminal ใหม่ รัน Service Server ```{code-block} bash ros2 run my_package service_server ``` * รอให้ Server แสดงข้อความ Service Server Ready! ก่อนเปิด Client เปิด Terminal ใหม่ รัน Service Client เพื่อทดสอบ ```{code-block} bash ros2 run my_package service_client ``` ```{image} images/c9/9.4.2.png :width: 80% :align: center ``` **ผลการทำงาน** 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" เพื่อยืนยันว่าได้รับ คำสั่ง และได้ทำการประมวลผลคำขอแล้ว 2. **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 ```{code-block} bash ros2 service call /add_two_ints example_interfaces/srv/AddTwoInts "{a: 7, b: 8}" ``` อธิบายคำสั่ง ```{code-block} bash ros2 service call ``` * ใช้สำหรับเรียก Service ใน ROS 2 จาก Terminal โดยทำงานร่วมกับ Service Server ที่มีชื่อเฉพาะ ```{code-block} bash /add_two_ints ``` * ชื่อของ Service ที่ต้องการเรียกใช้งาน ในที่นี้คือ Service ที่ให้บริการการบวกตัวเลขสองตัว (add_two_ints) ```{code-block} bash example_interfaces/srv/AddTwoInts ``` * ชนิดของ Service ที่ใช้ ในที่นี้คือ AddTwoInts ซึ่งกำหนดไว้ใน example_interfaces โดย Service นี้รับค่าตัวเลขสองตัวและคืนค่าผลลัพธ์จากการบวก ```{code-block} bash "{a: 7, b: 8}" ``` * ค่าพารามิเตอร์ที่ส่งไปยัง Service ได้แก่ a = 7 และ b = 8 ซึ่งเป็นค่าที่ Client ส่งให้ Service Server เพื่อให้ทำการบวกตัวเลขสองตัวนี้ **สามารถทดลองใช้เครื่องมือ RQT ในการเรียก Service** ```{image} images/c9/9.4.3.png :width: 80% :align: center ``` ```{image} images/c9/9.4.4.png :width: 80% :align: center ``` **สรุป** Service Server พร้อมรับ คำสั่ง จาก Client เมื่อ Client ส่ง คำสั่ง (7, 8), Server จะประมวลผล และคำนวณผลลัพธ์ (7 + 8 = 15) Server แสดงข้อความว่า "Received command: 7 + 8 = 15" เพื่อบ่งบอกว่าการประมวลผลเสร็จสิ้น และส่งผลลัพธ์กลับไปยัง Client ```{image} images/c9/9.4.5.png :width: 80% :align: center ``` **โจทย์** สร้าง Service ใน ROS 2 ที่ให้ Client สามารถส่งตัวเลขสองตัว (a และ b) เพื่อให้ Service Server ทำการ คูณ ตัวเลขทั้งสองแล้วส่งผลลัพธ์กลับไปยัง Client โดยใช้โปรแกรมที่เขียนด้วยภาษา Python ดังนี้ **รายละเอียด** 1. Service Server * รับคำสั่งจาก Client โดยมีสองตัวเลข a และ b * ทำการคูณตัวเลขทั้งสอง a * b * ส่งผลลัพธ์ของการคูณกลับไปยัง Client 2. Service Client * ส่งคำสั่งไปยัง Service Server โดยมีตัวเลขสองตัว a และ b * รอรับผลลัพธ์จาก Service Server * แสดงผลลัพธ์ที่ได้รับจาก Service Server **สรุปองค์ประกอบและรูปแบบการทำงานของ Service** องค์ประกอบของ ROS2 Service ```{list-table} :widths: 15 25 :header-rows: 1 * - **องค์ประกอบ** - **คำอธิบาย** * - 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: เหมือนการโทรศัพท์สอบถามข้อมูล มีการโต้ตอบแบบหนึ่งต่อหนึ่ง ```