ROS Service의 개념을 다시 복습해봅시다.
Service 개념 정리
Service의 중요한 특징 한 가지 추가하자면, 하나의 Service Server에 여러 Client가 request 할 수 있지만, Server는 동시에 여러 request를 처리하지 못합니다.
지난 topic 예시와 같이 ROS 1에서 ROS 2로 넘어오면서 변경된 커멘드 라인들을 살펴보겠습니다.
$ ros2 interface show turtlesim/srv/Spawn
float32 x
float32 y
float32 theta
string name # Optional. A unique name will be created and returned if this is empty
---
string name
서비스 타입 중간에 보이는 - - - 부분은 request와 response의 구분자라고 생각하시면 됩니다.
이번에 보여드릴 ROS 2 service 예시는 사진을 찍는 service server입니다.
# Terminal 1 - 여러분들만의 world를 사용하시면 더욱 좋습니다.
$ ros2 launch src_gazebo empty_world.launch.py
# Terminal 2
$ colcon build --packages-select py_service_tutorial
$ source install/local_setup.bash
$ ros2 run py_service_tutorial take_picture_server
[INFO] [turtle_circle_server]: Picture Taking Node Started
[INFO] [turtle_circle_server]: KimChi~
[INFO] [turtle_circle_server]: Image saved in 1672569224.png
# Terminal 3 - rqt 실행 후 service caller 실행 (plugins => services => service caller)
$ rqt
코드를 살펴보기 전에, 이를 어떻게 구현할 수 있을지 같이 생각해봅시다.
따라서, Node의 생성자는 다음과 같이 작성합니다.
class PictureNode(Node):
def __init__(self):
super().__init__('turtle_circle_server')
self.server = self.create_service(
SetBool, 'take_picture', self.take_picture_callback
)
self.subscriber = self.create_subscription(
Image, 'logi_camera_sensor/image_raw', self.sub_callback, 10
)
self.br = CvBridge()
self.is_request = False
$ ros2 interface show example_interfaces/srv/SetBool
# This is an example of a service to set a boolean value.
# This can be used for testing but a semantically meaningful
# one should be created to be built upon.
bool data # e.g. for hardware enabling / disabling
---
bool success # indicate successful run of triggered service
string message # informational, e.g. for error messages
def take_picture_callback(self, request, response):
if request.data is True:
self.get_logger().info('KimChi~')
self.is_request = True
response.success = True
response.message = "Successfully image written"
return response
def sub_callback(self, data):
if self.is_request:
current_frame = self.br.imgmsg_to_cv2(data, "bgr8")
file_name = str(self.get_clock().now().to_msg().sec) + '.png'
cv2.imwrite(file_name, current_frame)
self.get_logger().info(f'Image saved in {file_name}')
self.is_request = False
Gazebo에 다양한 물체를 배치시킨 뒤 사진을 찍어보는 것도 좋은 실습이 될 것입니다. 제가 준비한 dataset을 사용하여 여러분들만의 실습도 해보세요.
해당 모델들의 출처는 다음과 같습니다. (“The Effect of Color Space Selection on Detectability and Discriminability of Colored Objects.” arXiv preprint arXiv:1702.05421 (2017).)
WSL2를 사용하시는 분들께서는 터미널에서 explorer.exe . 를 입력하면 윈도우 파일 탐색기를 실행 가능합니다.
이번 예시는 ROS 1강의에서도 살펴본 바 있는, urdf를 사용하여 gazebo 상에 물체를 등장시키는 예시입니다.
ros2 launch src_gazebo wall_world.launch.py
ros2 run py_service_tutorial spawn_model
Gazebo에서 일정한 간격을 두고, 하얀색 박스가 등장하게 됩니다. 매번 박스가 등장할 때마다 service call이 이루어지는 것이지요.
from gazebo_msgs.srv import SpawnEntity
import rclpy
from rclpy.node import Node
class SpawnRobot(Node):
def __init__(self):
super().__init__('gazebo_model_spawner')
self.client = self.create_client(SpawnEntity, 'spawn_entity')
while not self.client.wait_for_service(timeout_sec=1.0):
self.get_logger().error('service not available, waiting again...')
create_client의 매개변수는 각각 다음과 같습니다.
main문은, 일반적인 node 실행과 다소 차이를 보입니다. Future라는 개념을 사용하여 이벤트 기반 spin을 구현하였습니다.
future = robot_spawn_node.send_req()
rclpy.spin_until_future_complete(robot_spawn_node, future)
if future.done():
try:
response = future.result()
except Exception:
raise RuntimeError(
'exception while calling service: %r' % future.exception()
)
else:
robot_spawn_node.get_logger().info('==== Service Call Done ====')
robot_spawn_node.get_logger().info(f'Status_message : {response.status_message}')
finally:
robot_spawn_node.get_logger().warn('==== Shutting down node. ====')
친구와 명확한 약속을 했다면, 그동안 다른 일을 할 수 있는 것처럼 Future는, 효율적인 비동기 프로그래밍을 위해 사용됩니다.
image from : brunch.co
def send_req(self):
...
self.future = self.client.call_async(self.req)
return self.future
spawn_parking_lot = Node(
package='py_service_tutorial',
executable='spawn_model',
name='spawn_model',
output='screen'
)
이 예시는 다음 Action에서도 활용되므로 잘 기억해두시기 바랍니다.
ROS 2에서 custom interface를 만들기 위해서는 C++ Package에서 작업이 이루어져야 합니다. C++ package는 build type ament_cmake를 사용하는 package였습니다.
$ ros2 pkg create --build-type ament_cmake <package-name>
$ ros2 pkg create --build-type ament_cmake custom_interfaces
사용할 수 있는 기본 데이터 형식들은 이 링크를 참고합니다.
이번에 만들어볼 custom interface는 다음 과제와 연결됩니다. 우선 저를 따라와 주세요.
float32 width
float32 height
---
bool success
해당 interface를 rclpy, rclcpp에서 사용 가능하도록 해봅시다. ROS 2는 DDS의 IDL(Interface Description Language)를 사용하여 다양한 언어에서 사용 가능한 데이터 타입을 만들 수 있습니다.
find_package(rosidl_default_generators REQUIRED)
rosidl_generate_interfaces(${PROJECT_NAME}
"msg/Num.msg"
"srv/AddThreeInts.srv"
"srv/TurningControl.srv"
"srv/TurtleJail.srv"
"action/Fibonacci.action"
"action/Maze.action"
)
<build_depend>rosidl_default_generators</build_depend>
<exec_depend>rosidl_default_runtime</exec_depend>
<member_of_group>rosidl_interface_packages</member_of_group>
$ colcon build --packages-select custom_interfaces && rosfoxy
Starting >>> custom_interfaces
Finished <<< custom_interfaces [0.62s]
Summary: 1 package finished [0.84s]
$ ros2 interface show custom_interfaces/srv/TurtleJail
float32 width
float32 height
---
bool success
custom interface의 사용 시 파이썬 패키지에서는 별도 작업 없이 사용 가능하지만 C++ 패키지는 코딩 시 CMakeLists.txt의 수정이 필요합니다. 이는 다소 난이도가 있어 강의에서 살피지는 않고 링크를 남겨두겠습니다.
topic과 service에 대해서 모두 살펴본 지금 상황에서 여러분들께 코딩 과제를 제시해보고자 합니다. 이번 코딩 과제에서 구현해야 하는 최종 결과는 다음과 같습니다.
rqt를 통해 turtle_jail_size service call을 하며, 감옥의 사이즈를 설정합니다.
거북이는 감옥을 벗어날 수 없으며, 감옥을 벗어나는 순간 원점으로 순간이동합니다.
# Terminal 1 – turtlesim실행
ros2 run turtlesim turtlesim_node
# Terminal 2 - turtle_teleop 실행
ros2 run turtlesim turtle_teleop_key
# Terminal 3 - 과제 프로그램 실행
ros2 run py_service_tutorial turtle_jail
[INFO] [turtle_jail_node]: === [Service Client : Ready to Call Service Request] ===
[INFO] [turtle_jail_node]: ==== [Service Server : Ready to receive Service Request] ====
# Terminal 4 - rqt의 service caller 실행 후 /turtle_jail_size에게 service call
cd ~/ros2_ws
source install/local_setup.bash
rqt
이번 예시는 custom interface를 사용하므로 local_setup.bash를 꼭 실행해주세요!
이 예시에서는 topic과 service를 모두 사용해야 합니다. 지금까지 학습한 내용들을 확인해볼 수 있는 좋은 기회가 될 것입니다.