ROS에서 서비스 클라이언트와 서버 구현하기

서비스 기반 통신 개요

ROS에서는 토픽(Topic) 외에도 서비스(Service)를 통해 노드 간 동기식 요청-응답 방식의 통신을 지원합니다. 이 방식은 클라이언트가 특정 작업을 요청하면 서버가 이를 처리하고 결과를 반환하는 모델입니다.

기능 패키지 생성

먼저, 새로운 ROS 패키지를 생성합니다. 아래 명령어는 의존성으로 roscpp, rospy, std_msgs, geometry_msgs, turtlesim을 포함합니다.

cd ~/catkin_ws/src
catkin_create_pkg learning_service roscpp rospy std_msgs geometry_msgs turtlesim

클라이언트 노드 구현

클라이언트는 다음 절차에 따라 작성됩니다:

  1. ROS 노드 초기화
  2. 서비스 연결용 클라이언트 객체 생성
  3. 요청 데이터 구성 및 전송
  4. 서버 응답 수신 및 처리

C++ 클라이언트 코드 예제

#include <ros/ros.h>
#include "learning_service/Person.h"

int main(int argc, char **argv) {
    ros::init(argc, argv, "person_requester");
    ros::NodeHandle nh;

    // /show_person 서비스가 활성화될 때까지 대기
    ros::service::waitForService("/show_person");
    ros::ServiceClient client = nh.serviceClient<learning_service::Person>("/show_person");

    // 요청 파라미터 설정
    learning_service::Person srv;
    srv.request.name = "Alice";
    srv.request.age = 25;
    srv.request.sex = learning_service::Person::Request::female;

    ROS_INFO("Sending request: name=%s, age=%d, sex=%d",
             srv.request.name.c_str(), srv.request.age, srv.request.sex);

    // 서비스 호출 수행
    if (client.call(srv)) {
        ROS_INFO("Response received: %s", srv.response.result.c_str());
    } else {
        ROS_ERROR("Failed to call service /show_person");
        return 1;
    }

    return 0;
}

파이썬 클라이언트 코드 예제

#!/usr/bin/env python

import rospy
from learning_service.srv import Person, PersonRequest

def request_person_info():
    rospy.init_node('person_requester')

    # 서비스 연결 대기
    rospy.wait_for_service('/show_person')
    
    try:
        proxy = rospy.ServiceProxy('/show_person', Person)
        req = PersonRequest()
        req.name = "Alice"
        req.age = 25
        req.sex = PersonRequest.female
        
        resp = proxy(req)
        rospy.loginfo("Server response: %s", resp.result)
        return resp.result
    except rospy.ServiceException as e:
        rospy.logerr("Service call failed: %s", str(e))

if __name__ == "__main__":
    request_person_info()

서버 노드 구현

서버는 다음과 같은 흐름으로 동작합니다:

  1. 노드 및 서비스 인스턴스 초기화
  2. 콜백 함수 등록
  3. 요청 수신 대기 루프 실행
  4. 처리 후 응답 반환

C++ 서버 코드 예제

#include <ros/ros.h>
#include <geometry_msgs/Twist.h>
#include <std_srvs/Trigger.h>

ros::Publisher cmd_pub;
bool enable_movement = false;

bool handle_command(std_srvs::Trigger::Request&,
                    std_srvs::Trigger::Response& res) {
    enable_movement = !enable_movement;
    ROS_INFO("Turtle movement: %s", enable_movement ? "Enabled" : "Disabled");
    
    res.success = true;
    res.message = "Command state toggled.";
    return true;
}

int main(int argc, char **argv) {
    ros::init(argc, argv, "movement_controller");
    ros::NodeHandle nh;

    cmd_pub = nh.advertise<geometry_msgs::Twist>("/turtle1/cmd_vel", 10);
    ros::ServiceServer srv = nh.advertiseService("/turtle_command", handle_command);

    ros::Rate rate(10);
    ROS_INFO("Movement controller ready.");

    while (ros::ok()) {
        if (enable_movement) {
            geometry_msgs::Twist msg;
            msg.linear.x = 0.6;
            msg.angular.z = 0.3;
            cmd_pub.publish(msg);
        }
        ros::spinOnce();
        rate.sleep();
    }

    return 0;
}

파이썬 서버 코드 예제

#!/usr/bin/env python

import rospy
import threading
from geometry_msgs.msg import Twist
from std_srvs.srv import Trigger, TriggerResponse

cmd_enabled = False
cmd_pub = rospy.Publisher('/turtle1/cmd_vel', Twist, queue_size=10)

def movement_loop():
    rate = rospy.Rate(10)
    while not rospy.is_shutdown():
        if cmd_enabled:
            twist = Twist()
            twist.linear.x = 0.6
            twist.angular.z = 0.3
            cmd_pub.publish(twist)
        rate.sleep()

def trigger_callback(req):
    global cmd_enabled
    cmd_enabled = not cmd_enabled
    rospy.loginfo("Movement command updated: %s", "ON" if cmd_enabled else "OFF")
    return TriggerResponse(success=True, message="State changed")

def start_server():
    rospy.init_node('movement_controller')
    rospy.Service('/turtle_command', Trigger, trigger_callback)
    
    thread = threading.Thread(target=movement_loop)
    thread.start()
    
    rospy.loginfo("Ready to accept movement commands.")
    rospy.spin()

if __name__ == '__main__':
    start_server()

CMakeLists.txt 설정

C++ 코드를 빌드하기 위해 CMakeLists.txt에 다음 항목을 추가합니다:

add_executable(person_client src/person_client.cpp)
add_executable(movement_controller src/movement_controller.cpp)

target_link_libraries(person_client ${catkin_LIBRARIES})
target_link_libraries(movement_controller ${catkin_LIBRARIES})

빌드 및 실행 절차

다음 명령어를 순차적으로 실행하여 시스템을 작동시킵니다:

cd ~/catkin_ws
catkin_make
source devel/setup.bash
roscore
rosrun turtlesim turtlesim_node
rosrun learning_service movement_controller
rosrun learning_service person_client

또는 파이썬 버전은 바로 실행 가능하며 별도 빌드가 필요 없습니다. 서비스 호출 테스트는 아래 명령어로 직접 확인할 수 있습니다:

rosservice call /turtle_command "{}"

태그: ROS 서비스 통신 C++ python 클라이언트-서버

6월 6일 16:04에 게시됨