9. Service Server & Client ์์ฑํด๋ณด๊ธฐ โ python
์ง๋ ์๊ฐ์ ์ด์ผ๊ธฐํ ๋ฐ์ ๊ฐ์ด service server๋ฅผ ์์ฑํ๋ ๋ฐฉ๋ฒ๋ถํฐ ์์ํด๋ณด๊ฒ ์ต๋๋ค.
์ด๋ฒ ์๊ฐ์ ์์ ๋ฅผ ์คํ์์ผ ๋ณด๊ฒ ์ต๋๋ค.
Turning tinybot
$ roslaunch gcamp_gazebo gazebo_world.launch
# gazebo ์๋ฎฌ๋ ์ด์
์ด ์์ ํ ์ผ์ง๋๊น์ง ๊ธฐ๋ค๋ ค ์ฃผ์ธ์!!
# ์ ํฐ๋ฏธ๋์ ์ฝ๋๋ค.
$ rosrun service_tutorial robot_turning_srv.py
# ๋ ๋ค๋ฅธ ํฐ๋ฏธ๋์ ์ฝ๋๋ค.
$ rosrun service_tutorial robot_turning_client.py
[INFO] [1610111418.391926, 0.000000]: ==== Robot Turning Server Started ====
> Type turning time duration:
์ด๋ฒ ์์ ๋, ์ฌ์ฉ์๋ก๋ถํฐ time, velocity input์ ๋ฐ๋๋ก ๋์ด ์์ผ๋, ์ ์ ํ ๊ฐ์ ์ ๋ ฅํด ์ฃผ์๋ฉด ๋ฉ๋๋ค.
- ์ฌ๊ธฐ์์ ์๋๋ ํ์ ํ๋ ๊ฐ์๋๋ฅผ ์๋ฏธํ๋ฉฐ, ๋จ์๋ radian์ ๋๋ค.
- ์๊ฐ์ ์์ ๊ฐ์๋๋ก ํ์ ํ๋ ์๊ฐ์ ์๋ฏธํ๋ฉฐ, ๋จ์๋ ์ด์ ๋๋ค.
๋ก๋ด์ด ์ํ๋ ์๊ฐ, ์ํ๋ ์๋๋ก ์์ง์๋์??
์๊ฐ๊ณผ ์๋๋ฅผ ์ ์กฐ์ ํด์ 90๋, 180๋ ๋ฑ ์ํ๋ ๊ฐ๋๋งํผ ๋ก๋ด์ด ํ์ ํ๋๋ก ํด๋ณด์ธ์ ๐
๊ทธ๋ผ, ์ฝ๋๋ฅผ ๋ถ์ํด ๋ณด๋๋ก ํ๊ฒ ์ต๋๋ค!! client, server ์์ผ๋ก ๋ถ์ํด ๋ณด๊ฒ ์ต๋๋ค.
robot_turning_client.py
#! /usr/bin/env python
"""
basic rospy service example
referenced from wiki.ros.org
url : http://wiki.ros.org/ROS/Tutorials/WritingServiceClient%28python%29
"""
import sys
import rospy
from service_tutorial.srv import ControlTurningMessage, ControlTurningMessageRequest
rospy.init_node("robot_turning_client")
rospy.loginfo("==== Robot Turning Server Started ====")
rospy.wait_for_service("/control_robot_angle")
service_client = rospy.ServiceProxy("/control_robot_angle", ControlTurningMessage)
request_srv = ControlTurningMessageRequest()
while not rospy.is_shutdown():
try:
t = input("> Type turning time duration: ")
vel = input("> Type turning angular velocity: ")
if vel > 1.5707:
raise ArithmeticError("Velocity too high !!")
request_srv.time_duration = t
request_srv.angular_vel = vel
break
except ArithmeticError as e:
rospy.logerr(e)
except Exception as e:
rospy.logerr("Not a number type number plz !!")
result = service_client(request_srv)
print(result)
- topic์์ message import๋ฅผ ํด์ฃผ์๋ ๊ฒ๊ณผ ๊ฐ์ด service์์ ์ฌ์ฉ๋๋ srv๋ฅผ import ํด์ค๋๋ค.
# ์ด์ topic message์์๋ ์ด๋ ๊ฒ ์ฌ์ฉํ์ฃ ?
from sensor_msgs.msg import LaserScan
# service๋ ์ด๋ ๊ฒ .srv๋ก import ํฉ๋๋ค.
from service_tutorial.srv import ControlTurningMessage, ControlTurningMessageRequest
client๊ฐ request๋ฅผ ํด์ผ ํ๋๋ฐ, ๊ทธ ๋์์ด ๋๋ server๊ฐ ์๋ค๋ฉด ๊ณค๋ํ ๊ฒ์ ๋๋ค.
์ด๋ฅผ ์ํด wait_for_service
๋ฅผ ํตํด server๊ฐ ๋์์ค์ธ์ง
ํ์ธํ๊ณ , ServiceProxy
๋ฅผ ์์ฑํฉ๋๋ค.
rospy.wait_for_service('/control_robot_angle')
- result ๋ถ์๋ server๋ก ๋ถํฐ ๋๋์์จ response๊ฐ ๋ด๊ฒจ ์์ต๋๋ค. ํด๋น ์์ ์์๋ ๋จ์ํ ์ฑ๊ณต ์ฌ๋ถ๋ง ํ๋จํ์์ง๋ง, ์ถ๊ฐ ์ฝ๋๋ฅผ ํตํด ๋ ๋ง์ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์๊ฒ ์ฃ ?
service_client = rospy.ServiceProxy('/control_robot_angle', ControlTurningMessage)
(...)
result = service_client(request_srv)
print(result)
๋๋จธ์ง ๋ถ๋ถ์ ์ฌ์ฉ์๋ก๋ถํฐ ์ ๋ ฅ์ ๋ฐ๊ณ , ์์ธ์ฒ๋ฆฌ๋ฅผ ํ๋ ๋ถ๋ถ์ ๋๋ค.
์ง๊ธ ๊ฐ์๋ฅผ ๋ฃ๋ ์์ ์์๋ ๋ชจ๋ ๋ฌธ์ ์์ด ์๋ฏธ์๋ ์ซ์๋ฅผ ์ ๋ ฅํ๊ฒ ์ง๋ง, ๋ด๊ฐ ๋ง๋ ์ฝ๋๋ฅผ ๋ค๋ฅธ ์ฌ๋์ด ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ์จ๊ฐ ์์ธ์ํฉ๋ค์ด ๋ฐ์ํ ์ ์์ ๊ฒ์ ๋๋ค. ์ด๋ฅผ ์ํด, ํ์ด์ฌ์ try/except๋ฌธ์ ์ฌ์ฉํ์์ต๋๋ค.
while not rospy.is_shutdown():
try:
t = input('> Type turning time duration: ')
vel = input('> Type turning angular velocity: ')
if vel > 1.5707:
raise ArithmeticError("Velocity too high !!")
request_msg.time_duration = t
request_msg.angular_vel = vel
break
except ArithmeticError as e:
rospy.logerr(e)
except Exception as e:
rospy.logerr("Not a number type number plz !!")
์ด๋ฒ์๋ service server
์ฝ๋๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค!
robot_turning_srv.py
#! /usr/bin/env python
import time
import rospy
from service_tutorial.srv import ControlTurningMessage, ControlTurningMessageResponse
from geometry_msgs.msg import Twist
def callback(request):
print(request)
# publish to cmd_vel
cmd_vel = Twist()
cmd_vel.angular.z = request.angular_vel
start_time = time.time()
print('\nRobot Turning...')
while time.time() - start_time < request.time_duration:
velocity_publisher.publish(cmd_vel)
print("Done ...")
cmd_vel.angular.z = 0.0
velocity_publisher.publish(cmd_vel)
response = ControlTurningMessageResponse()
response.success = True
return response
rospy.init_node('robot_turning_server')
rospy.loginfo("==== Robot Turning Server Started ====")
service_server = rospy.Service('/control_robot_angle', ControlTurningMessage, callback)
velocity_publisher = rospy.Publisher('/cmd_vel', Twist, queue_size=1 )
rospy.spin()
์ด์ ์ฝ๋์ ๋น๊ตํด์, import ๋ถ๋ถ์ ์ฐจ์ด๊ฐ ๋ณด์ ๋๋ค.
# client
from service_tutorial.srv import ControlTurningMessage, ControlTurningMessageRequest
# server
from service_tutorial.srv import ControlTurningMessage, ControlTurningMessageResponse
- ControlTurningMessage srv ํ์์ ๋ํด ์กฐํํด๋ณผ๊น์?
$ rossrv info ControlTurningMessage
[service_tutorial/ControlTurningMessage]:
uint32 time_duration
float64 angular_vel
---
bool success
[cpp_service_tutorial/ControlTurningMessage]:
uint32 time_duration
float64 angular_vel
---
bool success
์ด๊ฒ์ ํ์ด์ฌ ์ฝ๋์์ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ๋ [MessageName]Request, [MessageName]Response ์ด๋ฐ ํํ๋ฅผ ๊ฐ๊ฒ ๋ฉ๋๋ค. import ํ ๋ค์๋ ์ ์ ํ ๊ฐ์ ์ฑ์์ค์ผ ํ๊ฒ ์ง์
response = ControlTurningMessageResponse()
response.success = True
return response
ControlTurningMessage๋ ์ด๋์์ ๋์จ ๊ฒ์ผ๊น์? srv๋ค์ ์กฐํํ๋ ค๋ฉด, rossrv ์ปค๋งจ๋๋ฅผ ์ฌ์ฉํฉ๋๋ค.
$ rossrv list
control_msgs/QueryCalibrationState
control_msgs/QueryTrajectoryState
control_toolbox/SetPidGains
controller_manager_msgs/ListControllerTypes
controller_manager_msgs/ListControllers
controller_manager_msgs/LoadController
controller_manager_msgs/ReloadControllerLibraries
controller_manager_msgs/SwitchController
controller_manager_msgs/UnloadController
diagnostic_msgs/AddDiagnostics
diagnostic_msgs/SelfTest
dynamic_reconfigure/Reconfigure
(...)
์ง๊ธ์ ์์ ๋ฅผ ์ํด ์ ๊ฐ ๋ง๋ค์ด๋ srv๋ฅผ ์ฌ์ฉํ๊ณ ์์ผ๋ฉฐ, ๋ค์ ์์น์์ ํ์ธ์ด ๊ฐ๋ฅํฉ๋๋ค.
$ cd ~/gcamp_ws/src/gcamp_ros_basic/service_tutorial/srv
$ ls -al
ControlTurningMessage.srv
$ cat ControlTurningMessage.srv
uint32 time_duration
float64 angular_vel
---
bool success
์ต์ข ์ ์ผ๋ก, service_tutorial์์๋ ๋ค์๊ณผ ๊ฐ์ ์ธ๊ฐ์ง srv๋ฅผ ์ฌ์ฉํ ์ ์์ต๋๋ค.
- ControlTurningMessage
- ControlTurningMessageRequest
- ControlTurningMessageResponse
- ์ฝ๋๋ก ๋์์์, import ๋ถ๋ฅผ ๋ค์ ์ ๋ฆฌํ๋ฉด, client์๊ฒ response๋ ๋ถ๊ฐํ๋ request๋ง import ํ๋ฉด ๋ ๊ฒ์ด๋ฉฐ, ๋ค์๊ณผ ๊ฐ์ ์ด๋ฆ์ผ๋ก ๊ฐ์ ธ์ต๋๋ค.
from service_tutorial.srv import ControlTurningMessage, ControlTurningMessageResponse
- ์ด์ ๋ ์น์ํ
init_node
,loginfo
๊ฐ ๋ณด์ด๊ณ , Service Server class๋ฅผ ์์ฑํ๋ ๋ถ๋ถ์ด ๋ณด์ ๋๋ค.
rospy.init_node('robot_turning_server')
rospy.loginfo("==== Robot Turning Server Started ====")
service_server = rospy.Service('/control_robot_angle', ControlTurningMessage, callback)
velocity_publisher = rospy.Publisher('/cmd_vel', Twist, queue_size=1 )
rospy.spin()
- rospy.Sevice('service-server-name', 'srv', 'callback')
์ด์ฒ๋ผ, ๊ธฐ๋ณธ ์ ๋ณด๋ค๊ณผ ํจ๊ป ์ด๊ธฐํ๋ฅผ ํด์ค ๋ค, ์์ฒญ์ด ๋ค์ด์ฌ ๋๋ง๋ค ์ฝ๋ฐฑ์ด ์คํ๋ฉ๋๋ค.
rospy.Service('/control_robot_angle', ControlTurningMessageResponse, callback)
server๋ response๋ง ์ฌ์ฉํ๋๊น ControlTurningMessageResponse๋ฅผ ๋ฃ์ด์ผ ํ์ง ์๋? ํ๋ ์๊ฐ์ผ๋ก ์ค์ํ ์ ์์ต๋๋ค.
- ๋ค์ ์ฝ๋๋ก ๋์์์, ๋ณธ ์์ ๊ฐ ๊ฐ์๋, ์๊ฐ์ request ๋ฐ์ ๋ก๋ด์ ์์ง์ด๋ ๊ฒ์ด์๊ธฐ์ cmd_vel๋ก publishํ๋ publisher๋ ๋ง๋ค์์ต๋๋ค.
velocity_publisher = rospy.Publisher("/cmd_vel", Twist, queue_size=1)
- ๋ค์์ callback์ ๋๋ค. client๋ก๋ถํฐ request๊ฐ ์ฌ ๋๋ง๋ค ์คํ๋ ๋ถ๋ถ์ผ๋ก, request srv์์ ๊ฐ์๋์ ์๊ฐ์ ๊ฐ์ ธ์ cmd_vel๋ก publishํ๋ ๋ถ๋ถ์ ๋๋ค.
def callback(request):
print(request)
# publish to cmd_vel
cmd_vel = Twist()
cmd_vel.angular.z = request.angular_vel
start_time = time.time()
print('\nRobot Turning...')
while time.time() - start_time < request.time_duration:
velocity_publisher.publish(cmd_vel)
print("Done ...")
cmd_vel.angular.z = 0.0
velocity_publisher.publish(cmd_vel)
response = ControlTurningMessageResponse()
response.success = True
return response
๋ง์ง๋ง์ผ๋ก, ๋ชจ๋ ์์ ์ด ์๋ฃ๋์๋ค๋ฉด, client์๊ฒ ์ฑ๊ณตํ๋ค๋ response๋ฅผ ๋ณด๋ด์ผ ํ ๊ฒ์ ๋๋ค.
ํ์ฌ๋ ํ์
์ด bool์ด๊ธฐ ๋๋ฌธ์ True
๋ฅผ ๋ฃ์ด์ฃผ์์ต๋๋ค.
gazebo services
์ฐ๋ฆฌ๊ฐ roslaunch๋ฅผ ํตํด gazebo๋ฅผ ์คํ์ํค๋ ๊ฒ๊ณผ, ์ผ๋ฐ์ ์ผ๋ก gazebo๋ฅผ ์คํ์ํฌ ๋์๋ ์ฐจ์ด๊ฐ ์์ต๋๋ค.
roslaunch๋ก ์คํ์ํค๋ ๊ฒฝ์ฐ์๋ gazebo_ros๋ฅผ ํตํด rosnode๋ค๋ ๊ฐ์ด ์คํ๋๋ค๋ ์ ์ ๋๋ค. ๊ทธ๋์, ๋ชจ๋ธ์ ๋ถ๋ฌ์ค๊ณ , ์ ๊ฑฐํ๊ณ , ์์น๋ฅผ ์ด๋์ํค๊ณ , ์ค๋ ฅ ์์๋ฅผ ๋ฐ๊พธ๋ ๋ฑ์ ์๋ง์ service๋ค์ด ๊ฐ์ด ์คํ๋ฉ๋๋ค.
๊ทธ ์ค์์, ์ด๋ฒ ์๊ฐ์ ๋ฐฐ์ด ๋ชจ๋ธ spawn์ ํฌํจํ์ฌ, ๋ชจ๋ธ ์ ๊ฑฐ, ์๋ฎฌ๋ ์ด์ ์ด๊ธฐํ๋ฅผ requestํ๋ service client๋ค์ ์์๋ก ์ ๊ณตํ๋, gazebo๋ฅผ ํตํด ๋ ๋ง์ ์์ฉ๊ณผ ์ฐ์ต์ ํด๋ณด์๊ธธ ๋ฐ๋๋๋ค. ๐คฉ๐คฉ
#! /usr/bin/env python
"""
Fuctions for Gazebo model Spawn and Deletion
created by kimsooyoung : https://github.com/kimsooyoung
"""
import rospy
import rospkg
from std_srvs.srv import Empty
from geometry_msgs.msg import Pose
from gazebo_msgs.srv import SpawnModel, DeleteModel
def GazeboSpawnModel( name, pkg_name, pose, namespace='', frame='world'):
# model_name
model_name = name
# model_xml
rospack = rospkg.RosPack()
model_path = rospack.get_path(pkg_name)+'/models/'
with open (model_path + model_name + '.urdf', 'r') as xml_file:
model_xml = xml_file.read().replace('\n', '')
# robot_namespace
robot_namespace = namespace
# initial_pose
initial_pose = Pose()
initial_pose.position = pose.position
# z rotation -pi/2 to Quaternion
initial_pose.orientation = pose.orientation
# reference_frame
reference_frame = frame
# service call
spawn_model_prox = rospy.ServiceProxy('gazebo/spawn_urdf_model', SpawnModel)
result = spawn_model_prox(
model_name, model_xml,
robot_namespace, initial_pose,
reference_frame)
''' result fromat
bool success
string status_message
'''
print(result.status_message)
def GazeboDeleteModel(name):
'''
string model_name
---
bool success
string status_message
'''
delete_srv = DeleteModel()
model_name = name
spawn_model_prox = rospy.ServiceProxy('gazebo/delete_model', DeleteModel)
result = spawn_model_prox(model_name)
print(result.status_message)
def GazeboResetSimulation():
spawn_model_prox = rospy.ServiceProxy('gazebo/reset_simulation', Empty)
result = spawn_model_prox()
rospy.loginfo('Gazebo Simulation Reset')
rospy.init_node("gazebo_spawn_handler")
์ง๊ธ๊น์ง ROS์ service์ ๋ํด ๋ฐฐ์๋ณด์์ต๋๋ค. ๋ค์์ผ๋ก action์ ๋ํด ๋ฐฐ์๋ณด๊ฒ ์ต๋๋ค!! ๐จ๐ปโ๐ป