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
เขียนโค้ดลงในไฟล์ 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()
คำอธิบายโค้ด
นำเข้าโมดูลที่จำเป็น
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 สำหรับการบวกสองตัวเลข
การสร้าง 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
การสร้าง 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
การเริ่มต้น 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()
การเรียกใช้ 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
เขียนโค้ดลงในไฟล์ 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()
คำอธิบายโค้ด
นำเข้าโมดูลที่จำเป็น
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 สำหรับการบวกสองตัวเลข
การสร้าง 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 วินาที
การส่งคำขอ (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 และส่งค่าผลลัพธ์กลับไป
การเริ่มต้น 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
การเรียกใช้ 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',
เปิด 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
ผลการทำงาน
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" เพื่อยืนยันว่าได้รับ คำสั่ง และได้ทำการประมวลผลคำขอแล้ว
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
สรุป
Service Server พร้อมรับ คำสั่ง จาก Client เมื่อ Client ส่ง คำสั่ง (7, 8), Server จะประมวลผล และคำนวณผลลัพธ์ (7 + 8 = 15) Server แสดงข้อความว่า "Received command: 7 + 8 = 15" เพื่อบ่งบอกว่าการประมวลผลเสร็จสิ้น และส่งผลลัพธ์กลับไปยัง Client
โจทย์
สร้าง Service ใน ROS 2 ที่ให้ Client สามารถส่งตัวเลขสองตัว (a และ b) เพื่อให้ Service Server ทำการ คูณ ตัวเลขทั้งสองแล้วส่งผลลัพธ์กลับไปยัง Client โดยใช้โปรแกรมที่เขียนด้วยภาษา Python ดังนี้
รายละเอียด
Service Server
รับคำสั่งจาก Client โดยมีสองตัวเลข a และ b
ทำการคูณตัวเลขทั้งสอง a * b
ส่งผลลัพธ์ของการคูณกลับไปยัง Client
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: เหมือนการโทรศัพท์สอบถามข้อมูล มีการโต้ตอบแบบหนึ่งต่อหนึ่ง