본문 바로가기
자율주행

ROS(Robot Operating System) 프로그래밍

by icebear3000 2023. 4. 9.
반응형

Creating a ROS Workspace and Package

 ROS 개발의 첫 번째 단계는 ROS 패키지가 보관되는 ROS 작업 공간을 만드는 것입니다. 새 패키지를 생성하고, 기존 패키지를 설치하고, 새 실행 파일을 빌드 및 생성할 수 있습니다.

 

 먼저 ROS 작업 공간 폴더를 생성해야 합니다. 원하는 이름을 지정할 수 있으며 모든 위치에서 만들 수 있습니다. 일반적으로 이것은 우분투 home folder에 있습니다.

 

 새 터미널에서 다음 명령을 입력합니다. 이렇게 하면 catkin_ws라는 폴더가 생성되며 그 안에는 src라는 또 다른 폴더가 있습니다. ROS 작업 공간은 catkin 작업 공간이라고도 합니다.  

$ mkdir -p ~/catkin_ws/src

 

src 폴더의 이름은 변경하면 안됩니다. 그러나 작업 공간 폴더 이름은 변경할 수 있습니다. 명령을 입력한 후 cd 명령을 사용하여 src 폴더로 전환합니다.
$ cd catkin_ws/src

 

다음 명령은 새 ROS 작업 영역을 초기화합니다. 작업 영역을 초기화하지 않으면 패키지를 제대로 만들고 빌드할 수 없습니다.

$ catkin_init_workspace

 

src 폴더 안에 CMakeLists.txt가 있습니다. catkin 작업 공간을 초기화한 후 작업 공간을 빌드할 수 있습니다. 패키지 없이 작업 공간을 빌드할 수 있습니다. 작업 공간을 빌드하려면 catkin_ws/src 폴더에서 catkin_ws 폴더로 전환 후 다음 명령어를 입력하세요. catkin_make 명령어는 작업공간을 빌드합니다.

$ ~/catkin_ws/src$ cd ..

$ ~/catkin_ws$ catkin_make

 

이제 src 폴더 외에도 몇 개의 폴더를 볼 수 있습니다. 

 

 Src 폴더는 우리의 패키지가 보관되는 곳입니다. 패키지를 만들거나 빌드하려면, 해당 패키지를 src 폴더에 복사해야 합니다. 작업 공간을 만든 후, 작업 공간 환경을 추가하는 것이 중요합니다. 즉, 작업 공간 내부의 패키지에 접근할 수 있고 볼 수 있도록 작업 공간 경로를 설정해야 합니다. 이렇게 하려면, 다음 단계를 수행해야 합니다. 홈 폴더에서 .bashrc 파일을 열고, 파일 끝에 다음 줄을 추가하세요.

$ gedit .bashrc

source ~/catkin_ws/devel/setup.bash

 아시다시피, 홈 폴더의 .bashrc 스크립트는 새 터미널 세션이 시작될 때 실행됩니다. 그래서, .bashrc 파일에 삽입된 명령도 실행됩니다.


ROS Build System

ROS Catkin Workspace

 우리는 catkin_ws을 만들었지만, 어떻게 작동하는지에 대해서는 논의하지 않았습니다. 작업영역에 여러 개의 폴더가 있습니다. 각 폴더의 기능을 살펴보겠습니다.

 

src Folder

 catkin 작업 공간 폴더 내의 src 폴더는 리포지토리에서 새 패키지를 생성하거나 복제할 수 있는 위치입니다. ROS 패키지는 실행 파일이 src 폴더에 있을 때만 빌드하고 생성합니다. 워크스페이스 폴더에서 catkin_make 명령을 실행하면 src 폴더 내부를 확인하고 각 패키지를 빌드합니다.

 

build Folder

 ROS 작업 공간에서 catkin_make 명령을 실행하면 catkin 도구가 일부 빌드 파일과 중간 cache CMake 파일을 만듭니다. 이들은 빌드 폴더 안에 있습니다. 이러한 캐시(cache) 파일은 모든 파일을 다시 빌드하는 것을 방지합니다.

 

 catkin_make 실행 시 패키지 명령. 예를 들어 5개의 패키지를 빌드한 다음 src 폴더에 새 패키지를 추가하면 다음 catkin_make 명령 동안 새 패키지만 빌드됩니다. 이는 빌드 폴더 내의 캐시 파일 때문입니다. 빌드 폴더를 삭제하면 모든 패키지가 다시 빌드됩니다.

 

devel Folder

 catkin_make 명령을 실행하면 각 패키지가 빌드되고 빌드 프로세스가 성공하면 대상 실행 파일이 생성됩니다. 실행 파일은 현재 작업영역을 ROS 작업영역 경로에 추가하기 위한 셸 스크립트(shell script) 파일이 있는 개발 폴더 내에 저장됩니다. 이 스크립트를 실행하는 경우에만 현재 작업 영역 패키지에 액세스할 수 있습니다. 일반적으로 다음 명령을 사용하여 이 작업을 수행합니다:
source ~/<workspace_name>/devel/setup.bash

 모든 터미널 세션에서 워크스페이스 패키지에 액세스할 수 있도록 이 명령을 .bashrc 파일에 추가합니다. catkin 작업 공간을 설정하는 절차를 수행하면 다음 단계가 나타납니다.

 

install Folder

 대상 실행 파일을 로컬로 작성한 후 다음 명령을 실행하여 실행 파일을 설치합니다:
$ catkatkatkin_make install

 ROS 작업영역 폴더에서 실행해야 합니다. 이렇게 하면 작업 공간에 install 설치 폴더가 표시됩니다. 이 폴더는 설치 대상 파일을 보관합니다. 실행 파일을 실행하면 설치 폴더에서 실행됩니다.
 


Creating a ROS Package

 ROS 작업영역 생성을 마쳤습니다. 다음은 ROS 패키지를 생성하는 방법에 대해 살펴보겠습니다. ROS 패키지는 ROS 노드가 구성된 라이브러리 등의 위치입니다. 다음 명령을 사용하여 캣킨 ROS 패키지를 생성할 수 있습니다:
Synatx:
$ catkin_create_pkg ros_package_name package_dependencies

 

 패키지를 만드는 데 사용하는 명령은 catkin_create_pkg입니다. 이 명령의 첫 번째 매개 변수는 패키지 이름이며 패키지의 종속성이 그 뒤를 따릅니다. 예를 들어, 종속성이 있는 hello_world라는 패키지를 만들 예정입니다. catkin 작업 공간의 src 폴더에서 명령을 실행해야 합니다:

$ catkin_create_pkg hello_world roscpp rospy std_msgs

structure of a ROS package

 패키지 내부에는 src 폴더, package.xml,, CMakeLists.txt 및 include 폴더가 있습니다.:

CMakeLists.txt: 이 파일에는 패키지 내에 ROS 소스 코드를 빌드하고 실행 파일을 만드는 모든 명령이 있습니다.

package.xml: 기본적으로 XML 파일입니다. 주로 패키지 종속성, 정보 등을 포함합니다.
src: ROS 패키지의 소스 코드는 이 폴더에 보관됩니다. 일반적으로 C++ 파일은 src 폴더에 보관됩니다. Python 스크립트를 유지하려는 경우 패키지 폴더 내에 스크립트라는 다른 폴더를 생성할 수 있습니다.
include: 이 폴더에는 패키지 헤더 파일이 들어 있습니다. 이 파일은 자동으로 생성되거나 타사 라이브러리 파일이 저장됩니다.


Using ROS Client Libraries

 주제, 서비스, 메시지 등 다양한 ROS 개념을 다루었습니다. 이러한 개념을 어떻게 구현하기위해 ROS 클라이언트 라이브러리를 사용해야합니다. ROS 클라이언트 라이브러리는 ROS 개념을 구현하기 위한 함수가 있는 코드 모음입니다. 이러한 라이브러리 함수를 코드에 간단히 포함시켜 ROS 노드로 만들 수 있습니다. 클라이언트 라이브러리는 ROS 응용프로그램을 만드는 기본 제공 기능을 제공하므로 개발 시간이 절약됩니다.

 

다음은 기본 ROS 클라이언트 라이브러리입니다:

roscpp: 이것은 C++용 ROS 클라이언트 라이브러리입니다. 높은 성능으로 인해 ROS 애플리케이션 개발에 널리 사용됩니다.

rospy: 파이썬용 ROS 클라이언트 라이브러리입니다. ROS 노드를 생성하는 시간은 ROSpp보다 짧습니다. 빠른 프로토타이핑 애플리케이션에 이상적이지만 성능은 roscpp보다 약합니다.

roslisp: Lisp 언어의 ROS 클라이언트 라이브러리입니다. 주로 ROS의 모션 계획 라이브러리에서 사용되지만, Roscpp 및 Rospy만큼 인기가 많지는 않습니다.

 우리는 주로 roscpp와 rospy로 작업할 것입니다.


Header Files and ROS Modules

 C++로 코드를 작성할 때 첫 번째 섹션에는 헤더 파일이 포함됩니다. 마찬가지로 Python 코드를 작성할 때 첫 번째 섹션은 Python 모듈을 가져옵니다. 이 섹션에서는 ROS 노드로 가져와야 하는 중요한 헤더 파일 및 모듈에 대해 설명합니다. ROS C++ 노드를 만들려면 다음 헤더 파일을 포함해야 합니다:

#include "ros/ros.h"

 

 ros.h에는 ROS 기능을 구현하는 데 필요한 모든 헤더가 있습니다. 이 헤더 파일을 포함하지 않고는 ROS 노드를 만들 수 없습니다. ROS 노드에 사용되는 다음 유형의 헤더 파일은 ROS 메시지 헤더입니다. 노드에서 특정 메시지 유형을 사용하려면 메시지 헤더 파일을 포함해야 합니다. ROS에는 메시지 정의가 내장되어 있습니다, 새 메시지 정의를 만들 수도 있습니다.

 

 ROS에는 int, float, string 등의 표준 데이터 유형에 대한 메시지 정의를 가진 std_msgs라는 기본 제공 메시지 패키지가 있습니다. 예를 들어, 만약 우리가 우리의 코드에 문자열 메시지를 포함하고 싶다면, 우리는 다음을 사용할 수 있습니다:

#include "std_msgs/String.h"

 

 여기서 첫 번째 부분은 패키지 이름이고 다음 부분은 메시지 유형 이름입니다. 사용자 지정 메시지 유형이 있는 경우 다음 구문을 사용하여 이를 호출할 수 있습니다:

# include "msg_pkg_name/message_name.h"

 

 파이썬에서 우리는 ROS 노드를 만들기 위해 모듈을 가져와야 합니다. 우리가 가져와야 할 ROS 모듈은 'import rospy'입니다. rospy는 모든 중요한 ROS 기능을 가지고 있습니다. 메시지 유형을 가져오려면 C++에서처럼 특정 모듈을 가져와야 합니다.다음은 Python에서 문자열 메시지 유형을 가져오는 예입니다:
from std_msgs.msg import String

 

우리는 package_name.msg를 사용하고 필요한 메시지 유형을 가져와야 합니다.


Initializing a ROS Node

 ROS 노드를 시작하기 전에 먼저라는 첫 번째 함수가 노드를 초기화합니다. 이 단계는 모든 ROS 노드에서 필수 단계입니다. C++에서는 다음 코드 줄을 사용하여 초기화합니다:

int main(int argc, char **argv)
{
ros::init(argc, argv, "name_of_node")
.....................
}

 int main() 함수 다음에는 ROS 노드를 초기화하는 ros::init()를 포함해야 합니다. argc,argv 명령줄 인수를 init() 함수와 노드 이름으로 전달할 수 있습니다. 이것은 ROS 노드 이름이며, 우리는 ROS 노드 목록을 사용하여 목록을 검색할 수 있습니다.

 

Python에서는 다음 코드 줄을 사용합니다:

rospy.init_node('name_of_node', anonymous=True);

 

첫 번째 인수는 노드의 이름이고 두 번째 인수는 anonymous=True: 노드가 여러 인스턴스에서 실행될 수 있음을 의미합니다.


Printing Messages in a ROS Node

 ROS는 메시지를 기록하기 위한 API를 제공합니다. 이러한 메시지는 노드의 상태를 전달하는 읽을 수 있는 문자열입니다. C++에서 다음 함수는 노드의 메시지를 기록합니다:

ROS_INFO(string_msg,args): Logging the information of node
ROS_WARN(string_msg,args): Logging warning of the node
ROS_DEBUG(string_msg ,args): Logging debug messages
ROS_ERROR(string_msg ,args): Logging error messages
ROS_FATAL(string_msg ,args): Logging Fatal messages
Eg: ROS_DEBUG("Hello %s","World");


In Python:
rospy.logdebug(msg, *args)
rospy.logerr(msg, *args)
rospy.logfatal(msg, *args)
rospy.loginfo(msg, *args)
rospy.logwarn(msg, *args)


Creating a Node Handle

 노드를 초기화한 후 ROS 노드 및 publishing/subscribing a topic 과 같은 기타 작업을 시작하는 ::NodeHandle 인스턴스를 만들어야 합니다.

In C++:

ros::NodeHandle nh;

 

 노드의 나머지 작업은 nh 인스턴스를 사용합니다. Python에서는 핸들을 만들 필요가 없습니다. rospy 모듈이 내부적으로 핸들을 처리합니다.


Creating a ROS Message Definition

 주제를 게시하기 전에 ROS Message 정의를 작성해야 합니다. 메시지 정의는 다음 방법을 사용하여 작성됩니다.

 C++에서는 다음과 같은 코드 줄로 ROS 메시지의 인스턴스를 만들 수 있습니다. 예를 들어 std_msgs/String의 인스턴스를 만드는 방법은 다음과 같습니다: 

std_msgs::String msg;

 

 ROS 메시지의 인스턴스를 만든 후 다음 코드 행을 사용하여 데이터를 추가할 수 있습니다: 

msg.data = "String data"

 

In Python:

msg = String()
msg.data = "string data"


Publishing a Topic in ROS Node

 이 섹션에서는 ROS 노드에서 항목을 게시하는 방법을 보여 줍니다. 

In C++:

ros::Publisher publisher_object = node_handle.advertise<ROS message type >("topic_name",1000)

 

 publisher 개체를 만든 후 publish() 명령은 다음 항목을 통해 ROS 메시지를 보냅니다:

publisher_object.publish(message)

 

 아래는 chatter_pub은 ROS 게시자 인스턴스, 메시지 유형 std_msgs/String과 chatter를 주제 이름으로 게시하고 대기열 크기는 1000인 예시입니다.

Example:
ros::Publisher chatter_pub = nh.advertise<std_msgs::String>("chatter", 1000);
chatter_pub.publish(msg);

 

In Python:

publisher_instance = rospy.Publisher('topic_name', message_type, queue_size)

Example:
pub = rospy.Publisher('chatter', String, queue_size=10)
pub.publish(hello_str)


Subscribing a Topic in ROS Node

 topic를 publishing할 때 메시지 유형을 작성하고 topic를 통해 전송해야 합니다. topic를 subscribing할 때, 메시지는 topic로부터 수신됩니다.

In C++:

ros::Subscriber subscriber_obj = nodehandle.subscribe("topic_name", 1000, callback function)

 

 topicsubscribing할 때 topic 메시지 유형을 언급할 필요는 없지만, topic 이름과 콜백 기능을 언급해야 합니다. 콜백 기능은 topic을 통해 ROS 메시지가 수신되면 실행되는 사용자 정의 기능입니다. 콜백 내부에서는 ROS 메시지를 조작하여 메시지를 인쇄하거나 메시지 데이터를 기반으로 결정할 수 있습니다.

다음은 "chatterCallback" 콜백 기능이 있는 "chatter" 항목의 헤드라인 등록 예입니다(In C++):

ros::Subscriber sub = n.subscribe("chatter", 1000, chatterCallback);

 

In Python:

rospy.Subscriber("topic_name",message_type,callback funtion name")

rospy.Subscriber("chatter", String, callback)


Writing the Callback Function in ROS Node

 ROS topicsubscribe하고 해당 항목에 메시지가 도착하면 콜백 기능이 트리거됩니다. subscriber 기능에서 콜백 기능에 대한 언급을 본 적이 있을 것입니다. 다음은 C++의 콜백 함수의 구문 및 예제입니다:

void callback_name(const ros_message_const_pointer &pointer)
{
// Access data
pointer->data
}

 

다음은 ROS 문자열 메시지를 처리하고 데이터를 인쇄하는 방법을 보여줍니다(in C++):

void chatterCallback(const std_msgs::String::ConstPtr& msg)
{
  ROS_INFO("I heard: [%s]", msg->data.c_str());
}

 

In Python:

def callback(data):
    rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)


The ROS Spin Function in ROS Node

 subscription 또는 publishing를 시작한 후 subscribepublish 요청을 처리하기 위해 함수를 호출해야 할 수 있습니다. C++ 노드에서는 topicpublishing한 후에 ros::spinOnce() 함수를 호출해야 하며, topicsubscribing하는 경우에만 ros::spin() 함수를 호출해야 합니다. 둘 다 하는 경우에는 spinOnce() 함수를 사용합니다. Python에서는 spin() 기능이 없지만, Rospy를 사용할 수 있습니다.


The ROS Sleep Function in ROS Node

 node 안에 있는 루프 안에서 constant rate를 만들려면 ros::Rate를 사용할 수 있습니다. ros::Rate 인스턴스를 만들 수 있고 우리가 원하는 rate를 평가하고 언급합니다. 인스턴스를 생성한 후 속도를 적용하려면 인스턴스 내부의 sleep() 함수를 호출해야 합니다.

getting 10Hz in C++:

ros::Rate r(10); // 10 hz
r.sleep();

 

getting 10Hz in Python:

rate = rospy.Rate(10) # 10hz
rate.sleep()


Setting and Getting a ROS Parameter

 C++에서는 다음 코드 줄을 사용하여 코드의 매개 변수에 액세스합니다. 기본적으로 변수를 선언하고 node_handle 내의 getParam() 함수를 사용하여 원하는 매개 변수에 액세스해야 합니다.    

std::string global_name;
if (nh.getParam("/global_name", global_name))
{
... 

}

 

다음은 ROS 파라미터를 설정하는 방법을 보여줍니다. 이름과 값은 setParam() 함수 안에서 언급되어야 합니다.

nh.setParam("/global_param", 5);

 

In Python:

global_name = rospy.get_param("/global_name")
rospy.set_param('~private_int', '2')

 

 

반응형

댓글