꾸준하고 즐겁게

ROS의 Service 통신 방식 - [1 of 2] 본문

Hardware/ROS

ROS의 Service 통신 방식 - [1 of 2]

wj9183 2021. 6. 14. 18:31
728x90

1. 새로운 패키지 생성

cd ~/catkin_ws/src

먼저 경로를 이동해준다.

새로운 패키지를 만들어줄 것이다.

 

 

 catkin_create_pkg wj_tuto_service message_generation std_msgs roscpp

wj_tuto_service는 패키지명이고, 띄어쓰기를 기준으로 나뉘어있는 나머지들은 의존성이다.

cd wj_tuto_service

그리고 해당 경로로 이동해준다.

 

 

 

2. package.xml 파일 수정

gedit package.xml

vim package.xml

가장 간단한 파일부터 수정해보자.

편집기 다른 거 써도 상관없다.

이미지는 gedit을 사용했지만 이번에는 vim 편집기를 사용해보겠다.

사실 gedit으로 편집하고 스크린샷 찍으면 예쁘게 안나와서.

 

name의 패키지 이름이 제대로 되었는지 확인한다.

그리고 블럭 씌운 부분의 유지보수자 이메일과 이름을 바꿔주었다.

패키지에 대한 정보가 담겨있는 파일이다.

저 파트에는 빌드할 때 어떤 툴을 사용하고 어떤 패키지에 의존할 건지, 실행할 때는 또 어떻게 할 건지 등의 정보가 담겨있다.

따로 수정할 내용은 보이지 않는다.

:wq 명령어로 저장하고 꺼준다.

 

 

 

 

3. 서버 파일 생성

현재 디렉토리는 여기다.

mkdir srv
cd srv

먼저 디렉토리를 생성하고, 해당 경로로 이동한다.

 

 

vim wj_srv.srv

파일을 생성해준다.

이름은 바꿔줘도 되지만, 확장자명은 서버 파일이여야한다.

vim 켜고 끄기가 번거로우면 gedit 쓰면 된다.

 

화질구지다.

int64 a
int64 b
---
int64 result

메세지를 만들 때와 다르게, 중간에 하이픈이 들어가있다.

---을 기준으로 위에는 요청(request), 아래는 응답(response)할 때 가져다 쓸 변수들이다.

서비스는 응답과 요청이 동시에 필요하기 때문에 둘 다 넣은 것이다.

 

작성 후 저장해주었다.

 

 

 

 

 

 

 

 

4. server 노드 작성

cd .../src/

일단 경로부터 이동해주었다.

 

vim srv_server.cpp

 

#include "ros/ros.h" // 당연히 들어가야하는 부분
#include "wj_tuto_service/wj_srv.h" // message를 사용할 때랑 여기까지는 별 다를 게 없다.

bool calculation(wj_tuto_service::wj_srv::Request &req,
wj_tuto_service::wj_srv::Response &res)
{
        res.result = req.a + req.b;

        ROS_INFO("request : X = %ld, y = %ld", (long int)req.a,
(long int)req.b);
        ROS_INFO("sending back response : %ld", (long int)res.result);
        return true;
}


// 함수를 하나 만들었다.
// 함수명 뒤엔 패키지 이름::서비스 이름::..... 함수에 입력시킬 값들이다.
//      아까 srv 파일에서 ---하고 응답과 요청을 나눠놓았다. 그래서 그 뒤가 두 가지가 들어가는 것.

//      패키지 이름 서비스 이름은 맘대로 할 수 있는데, 리퀘스트 같은 건 바뀌면 안된다.
//      &req도 바꿔줄 수 있다. a로 하든 b로 하든...

// 반환 형태가 불이다. 즉 논리연산이다.(참,거짓)
// 그래서 리턴값이 트루다.



int main(int argc, char **argv)
{
        ros::init(argc, argv, "srv_server");
        ros::NodeHandle nh;         
        ros::ServiceServer server = nh.advertiseService("pizza", calculation);
        // 퍼블리셔 만들때 여기서 만들었었다.
        //service도 advertise는 해야하므로. advertiseServer
        //피자는 임의로 지은 서비스명, 칼큘레이션은 기능을 할 함수.

        ROS_INFO("ready srv server!!");
        ros::spin();    // 응답이 오길 기다리는 곳. while문 같은 것. 응답이 안오면 여기 갇히게 된다.

        return 0;
}

 

설명은 주석 안에 포함했다.

 

 

 

 

 

 

4. client 노드 작성

vim srv_client.cpp

클라이언트 노드를 vim 편집기로 열어주었다.

#include "ros/ros.h"
#include "wj_tuto_service/wj_srv.h"
#include <cstdlib> //atoll 이라는 함수를 쓰려고.

int main(int argc, char **argv)
{
	ros::init(argc, argv, "srv_client");
	if(argc != 3)
	{
		ROS_INFO("cmd : rosrun wj_tuto_service wj_srv_client arg0 arg1");
		ROS_INFO("arg0 : double number, arg1 : double number");
		return 1;
	// 이걸 왜 했냐면, 개발자 관점에서는 다 알고 있다.
	//	원래 arg 자리에 숫자 두개를 줘야한다. 노드가 필요로 한다.
	//	근데 사용하는 사람들은 모를 수가 있다.
	//	뭐가 잘못됐는지 화면에 띄워주기 위해서 이런 작업을 한 것. 조금 더 편하게 하려고.
	}
	ros::NodeHandle nh;
	ros::ServiceClient client = nh.serviceClient<wj_tuto_service::wj_srv>("pizza");
	
	//ros::Publisher
	//ros::Subscriber
	//ros::ServiceServer 각각을 만들 때 이렇게 썼었다. 기능을 넣을 때 항상 이런식으로 넣는 것이다.
	
	wj_tuto_service::wj_srv srv;

	srv.request.a = atoll(argv[1]); //atoll은 받아지는 값을 분리해서 넣는 함수다.
	srv.request.b = atoll(argv[2]);

	if(client.call(srv))	//요청을 보내는 게 client.call(srv)이다.
	{
		ROS_INFO("send srv, srv.Request.a and b : %ld, %ld", (long int)srv.request.a, (long int)srv.request.b);
		ROS_INFO("receive srv, srv.Response.result : %ld", (long int)srv.response.result);
	}
	else
	{
		ROS_ERROR("Failed to call service"); // ros error 함수를 쓰면 글자가 빨갛게 나온다.
		return 1;
	}
	return 0;
}

 역시 설명은 주석으로 달려있다.

 

 

 

 

 

 

 

 

다음 글로 이어진다.

https://gradient-descent.tistory.com/72

 

728x90