今天用Service Server 和 Client來寫個簡單的加法器,Server會接收兩個數字,然後回傳兩個數字的和。
Service Server的建立和Subscriber很像,是屬於被動的Node,會等待Client的請求,然後回傳結果。直接來看看程式碼吧。
py_srv
ros2 pkg create --build-type ament_python py_srv --dependencies rclpy example_interfaces
--dependencies
會自動將相依寫進package.xml
,這裡除了rclpy
之外,還需要example_interfaces
,因為我們要用到std_srvs
的AddTwoInts.srv
,官方範例的訊息格式。這邊介紹一下AddTwoInts.srv
的格式:
int64 a
int64 b
---
int64 sum
前面兩行是request,---
分隔線後面一行是response。package.xml
和setup.py
內的description, maintainer, license等等資訊。ros2_ws/src/py_srv/py_srv
下新增檔案service_member_function.py
:
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
return response
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
rclpy.spin(minimal_service)
rclpy.shutdown()
if __name__ == '__main__':
main()
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
除了我們熟悉的rclpy
之外,我們還需要example_interfaces.srv
,這邊是ROS2的官方範例的訊息格式AddTwoInts.srv
。
class MinimalService(Node):
def __init__(self):
super().__init__('minimal_service')
self.srv = self.create_service(AddTwoInts, 'add_two_ints', self.add_two_ints_callback)
接著是建構子的部分,先初始化繼承的Node
。接著使用Node
的create_service
建立Service,這邊的參數分別是srv的格式AddTwoInts
、Service的名稱add_two_ints
、還有callback functionadd_two_ints_callback
。
其中create_service
後面還有兩個參數create_service(srv_type, srv_name, callback, *, qos_profile=<rclpy.qos.QoSProfile object>, callback_group=None)
,分別是QoS Profile和callback_group,可以做更進階的設定,像是設定QoS的deadline等等,以及多個額外的callback functions,可以自己玩玩看。
def add_two_ints_callback(self, request, response):
response.sum = request.a + request.b
self.get_logger().info('Incoming request\na: %d b: %d' % (request.a, request.b))
return response
callback function的參數固定會是request
和response
,而request內則是依照srv的格式可以有多個attributes,這邊有a
和b
兩個變數。而response則是回傳的結果也可以有多個attributes,這邊只有sum
一個變數。最後回傳response。
def main(args=None):
rclpy.init(args=args)
minimal_service = MinimalService()
rclpy.spin(minimal_service)
rclpy.shutdown()
最後就是main function,這邊和Publisher一樣,先初始化rclpy
,接著建立MinimalService
,最後使用rclpy.spin
來讓Node持續運行,最後再關閉rclpy
。
由於我們已經在前面設定好dependencies,因此只需要在setup.py
中加入entry_points即可。這邊將service_member_function
加入entry_points並命名為service_member_function
已讓ros2 run
可以找到:
entry_points={
'console_scripts': [
'service_member_function = py_srv.service_member_function:main',
],
},
Service Client的則是和Publisher很像,是主動的Node,會主動的發送請求給Server,然後等待回傳結果。
我們在ros2_ws/src/py_srv/py_srv
下新增檔案client_member_function.py
:
import sys
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddTwoInts.Request()
def send_request(self, a, b):
self.req.a = a
self.req.b = b
self.future = self.cli.call_async(self.req)
rclpy.spin_until_future_complete(self, self.future)
return self.future.result()
def main(args=None):
rclpy.init(args=args)
minimal_client = MinimalClientAsync()
response = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(int(sys.argv[1]), int(sys.argv[2]), response.sum))
minimal_client.destroy_node()
rclpy.shutdown()
if __name__ == '__main__':
main()
import sys
from example_interfaces.srv import AddTwoInts
import rclpy
from rclpy.node import Node
這邊多引入了sys
,因為我們要從command line中使用sys.argv
來取得參數。
class MinimalClientAsync(Node):
def __init__(self):
super().__init__('minimal_client_async')
self.cli = self.create_client(AddTwoInts, 'add_two_ints')
建構子的部分,先初始化繼承的Node
,接著使用Node
的create_client
建立Service Client,這邊的參數分別是srv的格式AddTwoInts
、Service的名稱add_two_ints
。
while not self.cli.wait_for_service(timeout_sec=1.0):
self.get_logger().info('service not available, waiting again...')
self.req = AddTwoInts.Request()
這邊是等待Service Server,如果Server沒有啟動,就會一直等待,直到timeout_sec秒數到期。這邊的timeout_sec可以自己設定,預設是1秒。接著初始化request。接著建立一個AddTwoInts
的Request物件。
def send_request(self, a, b):
self.req.a = a
self.req.b = b
self.future = self.cli.call_async(self.req)
rclpy.spin_until_future_complete(self, self.future)
return self.future.result()
這個function會將request的兩個變數a
和b
設定好,接著使用call_async
來發送非同步的請求以便程式可以繼續運行,並且使用spin_until_future_complete
來等待回傳結果。最後回傳結果。
這邊官方有寫錯,並不是
__inti__
的while
loop在檢查future
而是rclpy.spin_until_future_complete
。
def main(args=None):
rclpy.init(args=args)
minimal_client = MinimalClientAsync()
response = minimal_client.send_request(int(sys.argv[1]), int(sys.argv[2]))
minimal_client.get_logger().info(
'Result of add_two_ints: for %d + %d = %d' %
(int(sys.argv[1]), int(sys.argv[2]), response.sum))
minimal_client.destroy_node()
rclpy.shutdown()
這邊會讀取使用者的輸入,並且將輸入的兩個數字傳送給send_request
,接著將回傳的結果印出來。
這邊需要在setup.py
中加入將client_member_function
加入entry_points並命名為client_member_function
:
entry_points={
'console_scripts': [
'service_member_function = py_srv.service_member_function:main',
'client_member_function = py_srv.client_member_function:main',
],
},
首先我們要先安裝dependencies,尤其這邊我們需要還沒安裝example_interfaces
:
cd ~/ros2_ws
rosdep install -i --from-path src --rosdistro foxy -y
接著build package:
colcon build --packages-select py_srv
最後Source:
source install/setup.bash
首先開啟一個terminal,執行Service Server:
ros2 run py_srv service_member_function
這個Service Server會等待Client的請求...
接著開啟另一個terminal,執行Service Client:
ros2 run py_srv client_member_function 1 2
就可以看到client 收到server的回傳結果了:
[INFO] [minimal_client_async]: Result of add_two_ints: for 1 + 2 = 3
同時也可以看到server的terminal印出了request的內容:
[INFO] [minimal_service]: Incoming request
a: 1 b: 2
其實Client的部分也跟Publisher一樣可以用指令來執行,會跟Topic統一到後面一起介紹。
說明 | ROS | ROS2 |
---|---|---|
service API | 在rospy 底下 |
在rclpy.node.Node 底下 |
service object 建立 | rospy.Service(srv_name, srv_type, callback) |
self.create_service(srv_type, srv_name, callback) |
client API | 在rospy 底下 |
在rclpy.node.Node 底下 |
client object 建立 | rospy.ServiceProxy(srv_name, srv_type) |
self.create_client(srv_type, srv_name) |
.srv 格式 |
一樣 | 一樣 |