ROS Basics: Service
요약
ROS1의 service를 사용하기 위해 service측과 client측에서 어떤 함수를 사용해야 하는 지에 대해 알아본다.
내용 정리
I. Service
I-I. Service definitions, request messages, and response messages
Service는 ROS Topic의 message와 동일하며, srv 파일로 정의된다. 그리고 rospy가 srv 파일에서 3 개의 classes를 만든다: service definition, request message, 그리고 response message. 각각의 class 이름은 지정한 srv 파일명에서 자동으로 생성된다.
e.g.,
my_package/srv/Foo.srv → my_package.srv.Foo
my_package/srv/Foo.srv → my_package.srv.FooRequest
my_package/srv/Foo.srv → my_package.srv.FooResponse
Service definition은 위 예시 코드처럼 import하고, 그리고 service initialization method에 전달해야 한다.
add_two_ints = rospy.ServiceProxy('service_name', my_package.srv.Foo)
위 예시는 client code에서 동일하게 함수 안에 있다.
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
Service request messages는 service를 호출(call)하기 위해 사용되는데, 일반적으론 이를 직접 사용할 일은 많지 않다고 한다.
Service response messages는 service에서 return된 값을 보관하기 위해 사용된다.
I-II. Service proxies
Service는 rospy.ServiceProxy를 통해 호출될 수 있고, service가 available할 때까지 대기시키기 위해서는 rospy.wait_for_service를 사용하면 된다.
rospy.wait_for_service('add_two_ints')
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
try:
resp1 = add_two_ints(x, y)
except rospy.ServiceException as exc:
print("Service did not process request: " + str(exc))
예시 코드에서 먼저 rospy.wait_for_service 이후 rospy.ServiceProxy를 실행하는 것을 확인할 수 있다. 그리고 try-except 에서 확인할 수 있듯이, 에러 발생 시 rospy.ServiceException이 나온다. 마지막 줄처럼 작성 시 error message를 확인할 수 있다.
rospy.ServiceProxy와 rospy.wait_for_service의 parameter는 다음과 같다:
rospy.ServiceProxy(name, service_class, persistent=False, headers=None)
rospy.wait_for_service(service, timeout=None)
※ Service call을 하기 위해 ROS node를 만들 필요는 없다.
Calling services
rospy.ServiceProxy는 아래 예시처럼, 함수처럼 callable하다.
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
add_two_ints(1, 2)
Arguments를 전달하는 방법은 세 가지가 있는데, explicit한 방법 하나, 두 가지 implicit한 방법이 있다. Explicit은 내가 직접 service request message instance를 만들어서 주는 것이고, implicit은 함수가 message를 만들어 주는 것이다.
Explicit style은 아래와 같이 Request class를 통해 publish한다.
req = rospy_tutorials.srv.AddTwoIntsRequest(1, 2)
resp = add_two_ints(req)
Implicit style with in-order arguments는 in-order라는 말처럼, 순서에 맞게 arguments를 입력해야 하며, 모든 arguments를 입력해야 한다.
resp = add_two_ints(1, 2)
Implicit style with keyword arguments는 python에서 특정 argument를 전달하는 방식처럼, argument 이름을 지정하여 전달한다. 위 방법과는 달리, 모든 argument를 전달하지 않아도 되며, 그럴 경우 지정된 default value가 전달된다.
resp = add_two_ints(a=1)
※ 그래도 몇몇 datatype은 자동으로 message가 만들어지지 않아서, instance를 만들고 전달해야 한다. 예를 들어, 위와 같은 형태인데 datatype이 std_msgs/String일 경우 아래처럼 전달해야 한다.
from std_msgs.msg import String
add_two_ints = rospy.ServiceProxy('add_two_ints', AddTwoInts)
add_two_ints(String(1), String(2))
Persistent connections
Persistent connection은 위 예시처럼 rospy.wait_for_service로 연결하는 것 말고도, rospy.ServiceProxy의 입력 인수를 persistent=True로 하여, 지속적으로 연결해, client가 different node에 연결할 때마다 service call을 하도록 할 수 있다.
I-III. Providing services
앞서 service를 호출하는 방법을 확인했다면, 이번에는 service를 제공하는 방법을 확인한다. 이는 rospy.Service instance를 만들어 수행하고, 해당 class instance의 initialize는 아래와 같다.
rospy.Service(name, service_class, handler, buff_size=65536)
name은 말 그대로 이름을, service_class는 srv 파일을 통해 만들어진 class이다. 그리고 handler를 통해 service response를 출력한다. 아래의 단순히 int add 예시에서 확인할 수 있다.
def add_two_ints(req):
return rospy_tutorials.srv.AddTwoIntsResponse(req.a + req.b)
def add_two_ints_server():
rospy.init_node('add_two_ints_server')
s = rospy.Service('add_two_ints', rospy_tutorials.srv.AddTwoInts, add_two_ints)
rospy.spin()
Service handler(위 예시에서 함수 add_two_ints)는 다음 5 개 return을 활용하면, 따로 response class instance를 만들지 않아도 바로 그 결과를 전달할 수 있다.
- None (failure)
- ServiceResponse(위 예시)
- tuple or list
- dict
- single-argument responses only: value of field.
tuple or list, dict, single-argument response의 예시는 다음과 같다.
tuple or list:
def add_two_ints(req):
return [req.a + req.b]
dict:
def add_two_ints(req):
return {'sum': req.a + req.b}
single-argument response:
def add_two_ints(req):
return req.a + req.b
(Waiting for) shutdown
서비스 종료는 두 가지 방법이 있다, service class의 method인 shutdown()을 실행하거나, spin() method를 사용해 셧다운 될때까지 현재 스레드를 점유하거나. spin()은 class method와 rospy.spin() 둘 다 사용할 수 있다.
shutdown() 함수를 사용해 종료하는 예제:
s = rospy.Service('add_two_ints', rospy_tutorials.srv.AddTwoInts, add_two_ints)
...
s.shutdown('shutdown reason')
spin() 함수를 사용해 shutdown 대기하는 예제:
s = rospy.Service('add_two_ints', rospy_tutorials.srv.AddTwoInts, add_two_ints)
s.spin() #returns when either service or node is shutdown
II. Service connection headers
Connection header는 ROS topic 과 service에 공통적으로 있는 기능인데, service와 client가 연결된 후 메타데이터를 확인할 수 있는 기능이다. 이 기능의 활용예로는 client의 callerid 확인 등이 있다.
Client 측에서는 ServiceProxy에서 header를 통해 metadata를 보낼 수 있다. Service 측에서는 _connection_header를 통해 이에 접근할 수 있다.
Client 측에서는 아래 예시와 같이 dictionary를 header에 전달해 metadata를 보낼 수 있다.
h = { 'cookies' : 'peanut butter' }
s = rospy.ServiceProxy('foo', Foo, headers=h)
Service 측에서는 아래 예시와 같이 _connection_header를 통해 수신된 정보에 접근할 수 있다.
def add_two_ints(req):
who = req._connection_header['callerid']
if 'cookies' in req._connection_header:
cookies = req._connection_header['cookies']
return AddTwoIntsResponse(req.a + req.b)
def add_two_ints_server():
rospy.init_node(NAME)
s = rospy.Service('add_two_ints', AddTwoInts, add_two_ints)
참고문헌
[1] https://wiki.ros.org/rospy/Overview/Services
Leave a comment