Study/[ROS] ROS Basic

ROS_Basics(4. Service. Server and Client)

soohwan_justin 2020. 11. 3. 16:39

이 포스트는 theconstructsim.com의 ROS BASICS 를 참고하였습니다.

 

https://youtu.be/DBFYZRMLr70

 

이번 포스트에서 다룰 내용은

- service가 무엇인가?

- 로봇의 service를 다루는 법

- service를 호출하는 법

 

시뮬레이션 모델은 turtlebot3 Waffle 모델을 사용합니다.

waffle 모델을 사용하기 위해서는 환경 변수를 추가하거나, bash shell에 해당 명령을 입력하면 됩니다.

 

$ export TURTLEBOT3_MODEL=waffle

 

또는, ~/.bashrc 를 수정하여 맨 밑에 export TURTLEBOT3_MODEL=waffle 를 추가합니다.

 

 

 

 

우리는 이전에 배운 topic만으로도 우리의 로봇을 위해 뭐든지간에 필요한 것을 어느 정도 대충은 할 수 있습니다. 많은 ROS package들은 단지 topic만을 사용해서도 잘 동작합니다.

 

그럼 우리는 왜 service에 대해 배워야 할까요?

 

가끔, topic은 사용하기에 너무 무거운 느낌이 있습니다. 데이터를 주고받을 필요가 딱히 없는 경우에도 topic은 계속 데이터를 주고받기 때문에 그만큼의 computing power가 낭비된다고 볼 수 있습니다.

 

Topics - Services - Actions

service를 사용하기 이전에, 이 것이 뭔지 이해하기 위해, 우리는 service를 topic, action과 비교하겠습니다.

 

우리가 어떤 로봇을 갖고 있다고 상상해보겠습니다. 이 로봇은 레이저 센서, 얼굴인식 시스템, 내비게이션 시스템을 장착했습니다. 레이저는 20hz로 센서의 모든 데이터를 publish 할 것입니다. 이 레이저 데이터는 내비게이션 같은 다른 ROS 시스템에서 항상 필요로 하기 때문에 topic을 사용하여 데이터를 주고받습니다.

 

하지만, 얼굴인식 시스템은 service를 이용할 수 있습니다. 만약 service를 이용한다면, 우리의 ROS 시스템은 로봇이 자기 앞에 있는 사람의 이름을 알려줄 때까지 대기할 것입니다.

 

내비게이션 시스템은 action을 이용할 것입니다. 우리의 ROS 프로그램은 로봇을 어딘가로 움직이게 하기 위해 action을 호출 할 것이고, 그 작업을 수행하는 동안 우리 프로그램은 다른 작업을 수행할 수도 있으며, 목표 위치로 움직이는 동안 "원하는 좌표까지 거리가 얼마나 남았는가?" 같은 피드백을 줍니다.

 

그래서 service와 action의 차이점이 무엇이냐?

service는 synchronous입니다. ROS 프로그램이 service를 호출하면, 프로그램은 그 service로부터 어떤 결과를 받을 때 까지는 동작하지 않습니다.

action은 asynchronous입니다. 마치 새로운 thread를 실행하는 것과 같습니다. ROS 프로그램이 action을 호출해도, 우리 프로그램은 다른 thread에서 action이 수행되는 동안 다른 작업을 할 수 있습니다.

 

결론적으로, 프로그램이 다른 service로부터 응답이 올 때 까지 동작하지 않는 프로그램을 만드려면 service를 사용하면 됩니다.

 

service의 info 데이터에 관련된 내용들은 다음과 같습니다

- Node : 그 service를 생성한 node

- Type : 이 service에 의해 사용되는 종류의 message. 이것은 topic의 구조와 같으며, topic처럼 message가 정의된 패키지에 정의됩니다.

Args : 이 service가 호출되었을 때 취할 수 있는 argument.

 

 

How to call and give a service

 


예제 3.1

 

이전에 생성했던 my_examples_pkg에서 진행합니다.

script 디렉토리에 simple_service_server.py라는 이름의 python code를 다음과 같이 작성합니다.

 

#! /usr/bin/env python
import rospy
from std_srvs.srv import Empty, EmptyResponse # you import the service message python classes generated from Empty.srv.
def my_callback(request):
print ("My_callback has been called")
return EmptyResponse() # the service Response class, in this case EmptyResponse
#return MyServiceResponse(len(request.words.split()))
rospy.init_node('service_server')
my_service = rospy.Service('/my_service', Empty , my_callback) # create the Service called my_service with the defined callback
rospy.spin() # maintain the service open.

 

그리고, 위 python code를 실행해봅니다.

 

$ rosrun my_examples_pkg simple_service_server.py

 

물론 아무 일도 일어나지 않습니다. 위 코드는 단지 service server를 만들고, 실행했을 뿐, 아직 이 service를 호출 한 것은 아무것도 없기 때문입니다. 하지만, rosservice list명령으로 현재 service가 있는지 확인할 수 있습니다.

 

 

이제 이 service를 호출해야 하는데, 즉, /my_service 라는 service를 호출하는 것입니다. TAB키를 두번 누르면 자동으로 필요한 형식의 service message를 완성해줍니다 새로운 터미널 창을 띄우고, 아래와 같이 입력해보세요.

 

$ rosservice call /my_service [TAB] + [TAB]

 

위 명령으로 service를 호출하면 python code를 실행했던 창에서는 아래와 같은 결과를 볼 수 있습니다.

 

 

- 예제 3.1. 끝


service는 topic과 다르게, Request와 Response로 나뉘어있으며, 서비스 형식 메시지의 확장자는 .srv입니다. .srv 파일에서 하이픈 3개 --- 로 Request와 Response부분으로 나누며, 클래스를 호출하는 것과 비슷하게 마침표(.)를 사용하여 해당 메시지를 사용할 수 있습니다.

 

topic에서는 topic message를 제공하는 node는 publisher, 받는 node는 subscriber라고 했습니다. service에서는 service message를 제공하는 node는 server, service를 요청하고 그 결과를 받는 node는 client라고 합니다.

 


- 예제 3.2

 

- 이전 예제에서 만들었던 my_examples_pkg 디렉토리에서 진행합니다. Empty service message를 받아 로봇이 원 궤적을 그리며 운동하게 하는 service servser를 생성하세요. 이 service의 이름은 /move_turtlebot_in_circle 입니다(로봇을 움직이게 하기 위해서는 이전의 topic 예제를 사용하세요).

예제 1.2를 참고해서 turtlebot_move_in_circle_service_server.py라는 이름으로 python code를 작성하세요.

 

- start_turtlebot_move_in_circle_service_server.launch 라는 파일을 만들어 위 python code를 실행하고, service message를 보내서 로봇이 원으로 움직이는지 확인해보세요.

 

- turtlebot_move_in_circle_service_client.py파일을 새로 만드세요. 이 코드는 service /move_turtlebot_in_circle을 호출해서 로봇이 움직이도록 해야합니다. 

 

- 마지막으로, call_turtlebot_move_in_circle_service_server.launch 파일을 만들어 실행해보세요.

 

요약하자면, server node와 client node가, /move_turtlebot_in_circle 라는 message를 주고받아 로봇이 움직이도록 하는 것입니다.

 

 

.

.

.

.

.

.

.

.

.

.

.

 

 

1. 먼저, 다음과 같이 turtlebot_move_in_circle_service_server.py 를 만듭니다.

 

#! /usr/bin/env python
import rospy
from std_srvs.srv import Empty, EmptyResponse # you import the service message python classes generated from Empty.srv.
from geometry_msgs.msg import Twist
def my_callback(request):
print ("My_callback has been called")
vel.linear.x = 0.5
vel.angular.z = 0.5
pub.publish(vel)
return EmptyResponse()
rospy.init_node('service_server')
my_service = rospy.Service('/move_turtlebot_in_circle', Empty , my_callback) # create the Service called my_service with the defined callback
pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)
vel = Twist()
rospy.spin() # maintain the service open.

 

 

2. 다음으로, turtlebot_move_in_circle_service_client.py 파일을 만듭니다.

 

#! /usr/bin/env python
import rospy
from std_srvs.srv import Empty, EmptyRequest # you import the service message python classes generated from Empty.srv.
rospy.init_node('service_client')
rospy.wait_for_service('/move_turtlebot_in_circle') # Wait for the service client /move_turtlebot_in_circle to be running
turtlebot_move_service_client = rospy.ServiceProxy('/move_turtlebot_in_circle', Empty) # Create the connection to the service
turtlebot_move_object = EmptyRequest()
result = turtlebot_move_service_client(turtlebot_move_object)
print result

 

 

3. call_turtlebot_move_in_circle_service_server.launch 파일과

 start_turtlebot_move_in_circle_service_server.launch 파일을 다음과 같이 만듭니다.

 

<launch>
<node pkg ="my_examples_pkg" type="turtlebot_move_in_circle_service_client.py" name="service_client" output="screen" />
</launch>
<launch>
<node pkg ="my_examples_pkg" type="turtlebot_move_in_circle_service_server.py" name="service_server" output="screen" />
</launch>

 

다음과 같이 실행해봅니다.

 

$ roslaunch turtlebot3_gazebo turtlebot3_empty_world.launch

$ rosrun robot_state_publisher robot_state_publisher

$ roslaunch my_examples_pkg start_turtlebot_move_in_circle_service_server.launch

$ roslaunch my_examples_pkg start_turtlebot_move_in_circle_service_server.launch

$ rviz

 

RViz에서 플러그인을 다음과 같이 추가하고, 결과를 확인해봅니다. RobotModel은 RViz 왼쪽 하단에서 Add를 클릭하여 추가하면 됩니다.

 

 

 

- 예제 3.2 끝

 


 

How to use Service messges in your code

service message가 컴파일 될 때마다, 3개의 message object들이 생성됩니다. 예를 들어 MyServiceMessage라는  message를 service한다고 가정하겠습니다. 이제 이 message가 컴파일 되면,

- MyServiceMessage

- MyServiceMessageRequest

- MyServiceMessageResponse

위와 같이 3개의 object들이 만들어집니다.

 

- MyServiceMessage는 service message 그 자체입니다. 이는 service server로의 연결을 만들기 위해 사용됩니다. 위의 예제에서는, 아래 코드에 대응됩니다

 

turtlebot_move_service_client = rospy.ServiceProxy('/move_turtlebot_in_circle', Empty) # Create the connection to the service

 

- MyserviceMessageRequest는 server에 보내기 위한 요청(request)을 만들기 위해 사용되는 object입니다. 우리가 사용하는 웹 브라우저가 웹 페이지를 요청하기 위해 HttpRequest object를 사용하여웹 서버에 연결하는 것과 같습니다. 따라서, 이 object는 요청을 service server에 보내기 위해 사용됩니다.

 

turtlebot_move_object = EmptyRequest()   result = turtlebot_move_service_client(turtlebot_move_object)

 

- MyServiceMessageResponse는 service가 끝날 때 마다 server로부터 client로 응답을 다시 보내기 위해 사용됩니다.

 

return EmptyResponse()

 

 

How to create your own service message

 

topic과 같이, srv도 사용자가 원하는 대로 만들 수 있습니다.

 


 

- 예제 3.3

 

다음과 같이 패키지를 만듭니다.

$ roscd;cd ..;cd src

$ catkin_create_pkg my_custom_srv_msg_pkg rospy

 

topic은 msg라는 디렉토리를 만들어서 message파일을 편집했지만, service는 srv라는 디렉토리를 만들어서 진행합니다. srv 파일의 이름은 MyCustomServiceMessage.srv라고 하겠습니다.

 

$ roscd my_custom_srv_msg_pkg/

$ mkdir srv

 

1. MyCustomServiceMessage.srv파일을 생성 후, 아래와 같이 편집합니다.

 

int32 duration
---
bool success

 

2. topic의 경우와 같이 CMakeLists.txt 파일과 package.xml 파일을 수정해야 합니다.

 

다음과 같이 CMakeListst.txt 파일을 수정합니다.

cmake_minimum_required(VERSION 3.0.2)
project(my_custom_srv_msg_pkg)
find_package(catkin REQUIRED COMPONENTS
rospy
std_msgs
message_generation
)
add_service_files(
FILES
MyCustomServiceMessage.srv
)
generate_messages(
DEPENDENCIES
std_msgs
)
catkin_package(
CATKIN_DEPENDS rospy
)
include_directories(
${catkin_INCLUDE_DIRS}
)

 

 

3. 다음과 같이 package.xml파일을 편집합니다

<?xml version="1.0"?>
<package format="2">
<name>my_custom_srv_msg_pkg</name>
<version>0.0.0</version>
<description>The my_custom_srv_msg_pkg package</description>
<maintainer email="user@todo.todo">user</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_depend>message_generation</build_depend>
<build_export_depend>rospy</build_export_depend>
<exec_depend>rospy</exec_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>std_msgs</exec_depend>
<build_export_depend>message_runtime</build_export_depend>
<exec_depend>message_runtime</exec_depend>
<export>
</export>
</package>

 

4. catkin_make를 합니다.

 

$ roscd;cd ..
$ catkin_make
$ source devel/setup.bash

 

5. 다음 명령어로 service message가 제대로 만들어졌는지 확인해봅니다.

 

$ rossrv show my_custom + [TAB] + [TAB]

 

 

6. 다음과 같이 python code를 작성하여 custom service message를 사용해봅니다. 아래 코드를 실행 후 rosservice call 명령어로 service message를 보내면 해당 값에 따라 True나 False로 응답해줍니다.(python 파일명은 임의로 정하세요)

#! /usr/bin/env python
import rospy
from my_custom_srv_msg_pkg.srv import MyCustomServiceMessage, MyCustomServiceMessageResponse # you import the service message python classes
# generated from MyCustomServiceMessage.srv.
def my_callback(request):
print ("Request Data==> duration="+str(request.duration))
my_response = MyCustomServiceMessageResponse()
if request.duration > 5.0:
my_response.success = True
else:
my_response.success = False
return my_response # the service Response class, in this case MyCustomServiceMessageResponse
rospy.init_node('service_client')
my_service = rospy.Service('/my_service', MyCustomServiceMessage , my_callback) # create the Service called my_service with the defined callback
rospy.spin() # maintain the service open.

 

7. 위 코드를 실행 후, 아래와 같이 service message를 보내봅니다.

 

$ rosrun my_custom + [TAB] + [TAB] + [TAB]

$ rosservice call /my_service + [TAB]

 

service를 호출할 때마다 python code를 실행한 터미널에서는 line 10에서의 print문이 출력되고, service를 호출한 터미널에서는 line 16에서의 my_response가 리턴되어 출력되는 것을 볼 수 있습니다.

 

 

- 예제 3.3 끝

 


- 예제 3.4

 

- 예제 3.3의 패키지 안에 turtlebot_move_custom_service_server.py 라는 파일을 만들고, 예제 3.3에서 Empty service message를 사용하여 로봇을 원으로 움직이게 했던 코드를 수정해서 /move_turtlebot_in_circle_custom 이라는 새로운 service를 사용 할 것입니다. 이 service는 3.2 예제에 사용했던 service를 그대로 사용하세요.

 

- /move_turtlebot_in_circle_custom 을 이용해서 turtlebot의 움직임을 제어할 것입니다. 특정한 duration 시간동안 turtlebot이 움직이도록 하세요. 이 시간이 끝나면, 움직임을 멈추고 service server가 True(success로 정의되어있습니다.)를 리턴하도록 하세요.

 

시간을 세는 방법은 rospy.Rate()와 rospy.sleep()함수를 이용하면 됩니다. rospy.Rate(1)이라고 하면 1초의 rate를 갖는 object를 선언합니다 그리고 그 object.sleep() 이라는 함수를 호출하면 1초의 딜레이가 생깁니다. 예를 들어, rate=rospy.Rate(1) 이라고 선언한 후, rate.sleep() 을 호출하면 1초의 딜레이가 생기는 것입니다.

 

- 위 python code를 실행할 start_turtlebot_move_custom_service_server.launch 파일을 만들어서 실행해보세요.

 

- /move_turtlebot_in_circle_custom 을 호출할 때마다 제대로 동작하는지 확인해보세요.

 

- /move_turtlebot_in_circle_custom을 호출하는 call_turtlebot_move_custom_service_server.py 를 만들어서 로봇이 움직이도록 해보세요. 이전의 예제들을 참고하세요.

 

- call_turtlebot_move_custom_service_server.py를 실행하는

call_turtlebot_move_custom_service_server.launch를 만들어서 실행해보세요.

 

.

.

.

.

.

.

.

.

.

.

.

 

 

1. 다음과 같이 server에 대한 python code를 작성합니다.

 

turtlebot_move_custom_service_server.py

 

#! /usr/bin/env python
import rospy
from my_custom_srv_msg_pkg.srv import MyCustomServiceMessage, MyCustomServiceMessageResponse
from geometry_msgs.msg import Twist
def my_callback(request):
print ("Request Data==> duration="+str(request.duration))
my_response = MyCustomServiceMessageResponse()
if request.duration > 0.0:
my_response.success = True
i = 0
while i < request.duration:
vel.linear.x = 0.5
vel.angular.z = 0.5
pub.publish(vel)
rate.sleep()
i += 1
print (i)
else:
my_response.success = False
vel.linear.x = 0.0
vel.angular.z = 0.0
pub.publish(vel)
return my_response
rospy.init_node('service_server')
my_service = rospy.Service('/move_turtlebot_in_circle_custom', MyCustomServiceMessage , my_callback)
pub = rospy.Publisher('/cmd_vel', Twist, queue_size=1)
vel = Twist()
rate = rospy.Rate(1)
rospy.spin()

 

2. 다음과 같이 client 부분의 python code를 작성합니다.

 

turtlebot_move_custom_service_client.py

#! /usr/bin/env python
import rospy
from my_custom_srv_msg_pkg.srv import MyCustomServiceMessage, MyCustomServiceMessageRequest
rospy.init_node('service_client')
rospy.wait_for_service('/move_turtlebot_in_circle_custom') # Wait for the service client /move_turtlebot_in_circle to be running
turtlebot_move_service_client = rospy.ServiceProxy('/move_turtlebot_in_circle_custom', MyCustomServiceMessage) # Create the connection to the service
turtlebot_move_object = MyCustomServiceMessageRequest()
turtlebot_move_object.duration = 5
result = turtlebot_move_service_client(turtlebot_move_object)
print result

 

3. launch 디렉토리에 다음과 같이 launch 파일들을 만듭니다.

 

turtlebot_move_custom_service_server.launch

<launch>
<node pkg ="my_custom_srv_msg_pkg" type="turtlebot_move_custom_service_server.py" name="service_server" output="screen" />
</launch>

 

turtlebot_move_custom_service_client.launch

 

<launch>
<node pkg ="my_custom_srv_msg_pkg" type="turtlebot_move_custom_service_service.py" name="service_server" output="screen" />
</launch>

 

4. turtlebot_move_custom_service_client.py와 turtlebot_move_custom_service_server.py 파일에 실행 권한을 부여하고, roslaunch 명령으로 실행합니다.

 

 

- 예제 3.4 끝

'Study > [ROS] ROS Basic' 카테고리의 다른 글

ROS Basics(5. Action, Server and Client)  (0) 2021.08.29
ROS Basics(3. Topic, Subscriber)  (0) 2020.11.02
ROS Basics(2. Topic, Publisher)  (0) 2020.10.31
ROS Basics(1)  (0) 2020.10.31