ROS 프로그래밍 전에 알아둬야 할 사항
- 표준 단위: SI 단위 사용
- 좌표 표현 방식: x(forwoard), y(left), z(up) - 오른손 법칙
- 프로그래밍 규칙: 패키지, 토픽, 파일, 네임스페이스 명명 규칙을 따라야 한다. (구글링으로 확인)
ROS 메시지 통신
이전에 설명했기 때문에 넘어가겠다.
파라미터 서버는 매개변수의 마스터 역할을 한다. 유저가 정해놓은 파라미터를 저장하고 있고, 임의의 명령을 통해 파라미터 값을 바꿀 수 있다. 해당 파라미터에 관련된 노드가 그 값을 참조하게 된다.
Topic / Publisher / Subscriber
1) 패키지 생성
ROS에서는 단방향 통신일 때 'Topic'이라는 메시지 통신을 사용한다.
이때 송신 측을 'Publisher', 수신 측을 'Subscriber'라고 한다.
cd ~/catkin_ws/src
catkin_create_pkg ros_tutorials_topic message_generation std_msgs rospy(C++이면 roscpp)
- 1. ROS 설치(한줄 설치인 경우)를 하면, Home 디렉토리 밑에 catkin_ws가 생긴다. 그곳으로 이동한다.
- 2. ros_tutorials_topic이라는 패키지를 만든다. 이때 패키지는 std_msgs와 rospy 를 쓸 것이다. 이걸 포함해서 패키지를 만드는 명령어가 2번째 줄과 같다. std_msgs는 정수, 실수, 불린 같은 메시지를 말하며, 나중에 카메라 데이터 메시지 등을 보내려면 다른 패키지를 이용하면 되겠다.
- 위 명령어를 한 줄씩 입력하면 catkin_ws/src 밑에 message_generation이라는 디렉토리가 생긴다.
cd ros_tutorials_topic/
ls
CMakeLists.txt package.xml src
- 만든 디렉토리로 이동하고 ls를 치면 2개의 파일(CMakeLists.txt, package.xml)과 src 폴더가 생겼음을 확인할 수 있다. 만약 roscpp 패키지로 생성했다면 C++의 헤더파일 폴더인 include 또한 생성된다.
- src: 소스 코드 폴더
- CMakeLists.txt: 빌드 설정 파일
- package.xml: 패키지 설정 파일
파이썬은 여기에 추가로 scripts 디렉토리를 생성한다. 파이썬이 스크립트 언어이기 때문에 src 폴더와는 별개로 사용하겠다.
mkdir scripts
- 위 명령어를 입력하면 scripts 폴더가 생성된다.
2) 패키지 설정 파일(package.xml) 수정 (필수사항 아님)
ROS의 필수 설정 파일 중 하나인 package.xml은 패키지 정보를 담은 XML 파일로서 패키지 이름, 저작자, 라이선스, 의존성 패키지 등을 기술하고 있다. 혹여 나중에 본인이 공식홈페이지(wikiros)에 이를 올릴 예정이라면 이 부분을 수정하여 올리도록 하자.
gedit package.xml
gedit라는 메모장으로 package.xml을 연다. 그러면 다음과 같이 적혀 있을 것이다. 여기서 내부에 있는 내용 중 웹사이트나 이메일 부분만 본인의 정보로 변경하고 나머지는 웬만하면 건들지 말도록 하자. 어떤 패키지를 의존하는지가 적혀있기 때문이다.
<?xml version="1.0"?>
<package format="2">
<name>ros_tutorials_topic</name>
<version>0.0.0</version>
<description>The ros_tutorials_topic package</description> #나중에 패키지 올릴때 들어가는 설명 부분
<!-- One maintainer tag required, multiple allowed, one person per tag -->
<!-- Example: -->
<!-- <maintainer email="본인 이메일">본인 이름</maintainer> -->
<maintainer email="본인이메일">본인 이름</maintainer>
<!-- One license tag required, multiple allowed, one license per tag -->
<!-- Commonly used license strings: -->
<!-- BSD, MIT, Boost Software License, GPLv2, GPLv3, LGPLv2.1, LGPLv3 -->
<license>TODO</license>
<!-- Url tags are optional, but multiple are allowed, one per tag -->
<!-- Optional attribute type can be: website, bugtracker, or repository -->
<!-- Example: -->
<!-- <url type="website">본인 웹사이트</url> -->
<!-- Author tags are optional, multiple are allowed, one per tag -->
<!-- Authors do not have to be maintainers, but could be -->
<!-- Example: -->
<!-- <author email="저자 이메일(지금은 본인 이메일)">저자 이름(본인 이름)</author> -->
<!-- The *depend tags are used to specify dependencies -->
<!-- Dependencies can be catkin packages or system dependencies -->
<!-- Examples: -->
<!-- Use depend as a shortcut for packages that are both build and exec dependencies -->
<!-- <depend>roscpp</depend> -->
<!-- Note that this is equivalent to the following: -->
<!-- <build_depend>roscpp</build_depend> -->
<!-- <exec_depend>roscpp</exec_depend> -->
<!-- Use build_depend for packages you need at compile time: -->
<!-- <build_depend>message_generation</build_depend> -->
<!-- Use build_export_depend for packages you need in order to build against this package: -->
<!-- <build_export_depend>message_generation</build_export_depend> -->
<!-- Use buildtool_depend for build tool packages: -->
<!-- <buildtool_depend>catkin</buildtool_depend> -->
<!-- Use exec_depend for packages you need at runtime: -->
<!-- <exec_depend>message_runtime</exec_depend> -->
<!-- Use test_depend for packages you need only for testing: -->
<!-- <test_depend>gtest</test_depend> -->
<!-- Use doc_depend for packages you need only for building documentation: -->
<!-- <doc_depend>doxygen</doc_depend> -->
<buildtool_depend>catkin</buildtool_depend>
<build_depend>message_generation</build_depend>
<build_depend>rospy</build_depend>
<build_depend>std_msgs</build_depend>
<build_export_depend>rospy</build_export_depend>
<build_export_depend>std_msgs</build_export_depend>
<exec_depend>rospy</exec_depend>
<exec_depend>std_msgs</exec_depend>
<!-- The export tag contains other, unspecified, tags -->
<export>
<!-- Other tools can request additional information be placed here -->
</export>
</package>
- buildtool은 'catkin'을 이용한다.
3) 빌드 설정 파일(CMakeLists.txt) 수정 및 publisher&subscriber node 생성
roscpp의 CMakeLists를 수정하는 방법은 유튜브에 업로드되어 있는 표윤석 박사님의 강의를 참고하면 좋다.
여기서는 rospy의 수정 방법에 대해 설명한다.
Publisher node 만들기
Visual Studio Code나 gedit를 이용하여 이전에 만든 scripts 폴더에 publisher가 될 파일인 talker.py를 생성한다.
chmod +x talker.py
생성 후 터미널에 위의 코드를 입력하여 모든 사람이 실행 가능한 파일이 되도록 바꾼다.
scripts 위치에서 터미널에 ls를 치면 기존에는 흑색이었던 글씨가 초록색으로 바뀌게 될 것이다.
그리고 다음의 간단한 퍼블리셔 python 코드를 복사 붙여넣기 한다.
#!/usr/bin/env python
# license removed for brevity
import rospy
from std_msgs.msg import String
def talker():
pub = rospy.Publisher('chatter', String, queue_size=10)
rospy.init_node('talker', anonymous=True)
rate = rospy.Rate(10) # 10hz
while not rospy.is_shutdown():
hello_str = "hello world %s" % rospy.get_time()
rospy.loginfo(hello_str)
pub.publish(hello_str)
rate.sleep()
if __name__ == '__main__':
try:
talker()
except rospy.ROSInterruptException:
pass
- rospy와 string을 임포트한다.
- rospy의 Publisher의 객체로 pub을 만든다. 토픽의 이름은 chatter이고, String의 형태로 내보낸다. 큐 사이즈는 10이다.
- rospy.init_node로 노드를 생성한다. 노드의 이름은 'talker'이며 anonymous를 True로 두면 이름이 겹치지 않도록 talker1, talker2처럼 알아서 노드의 이름을 변경해준다.
- rospy.Rate(10)을 하면 10Hz로 초당 10번 정도 토픽을 뿌리게 된다.
- while not 구문은 rospy.is_shutdown()이 False일 때 즉, ctrl+c(종료키)를 누르지 않은 상태일 때 반복된다. hello_str이라는 객체에 hello world라는 메시지와 토픽을 주고받는 시간인 rospy.get_time()이 추가되어 대입된다.
- rospy.loginfo는 실행한 터미널에 전송될 메시지를 보여준다.
- pub.publish는 subscriber로 메시지를 전송한다.
- rate.sleep()은 초당 10번을 보낼 때 즉 1번에 100ms정도를 쓰는데 이때 보내고 남는 시간은 대기한다는 의미이다.
- 아래 if문은 파이썬에서 기본적인 문법으로 나중에 이 py 파일을 다른 곳에서 임포트할 때 아래의 if문은 실행되지 않도록 하는 것이다. 아래의 if문은 해당 py파일을 직접 실행할 때만 수행되는 부분이다. 계속해서 talker 함수를 실행하다가 예외가 발생하면 pass한다.
이후 CMakeList.txt에서 다음과 같이 수정한다.
catkin_install_python(PROGRAMS
scripts/talker.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
Subscriber node 만들기
동일한 방법으로 이번에는 listener.py 파일을 만들고 실행 권한을 부여한다.
chmod +x listener.py
그리고 다음을 복사 붙여넣기한다.
#!/usr/bin/env python
import rospy
from std_msgs.msg import String
def callback(data):
rospy.loginfo(rospy.get_caller_id() + "I heard %s", data.data)
def listener():
# In ROS, nodes are uniquely named. If two nodes with the same
# name are launched, the previous one is kicked off. The
# anonymous=True flag means that rospy will choose a unique
# name for our 'listener' node so that multiple listeners can
# run simultaneously.
rospy.init_node('listener', anonymous=True)
rospy.Subscriber("chatter", String, callback)
# spin() simply keeps python from exiting until this node is stopped
rospy.spin()
if __name__ == '__main__':
listener()
- callback은 마이크로프로세서의 인터럽트와 비슷한 개념이다. 대기하고 있다가 publisher가 보낸 메시지를 받으면 받았다는 신호를 loginfo로 터미널에 내보내게 된다. get_caller_id()는 본인의 id이고 차례대로 글씨와 받은 데이터이다.
- 퍼블리셔 때와 동일하게 rospy.init_node로 노드를 생성한다. 노드의 이름은 'listener'이고 이름이 겹치지 않도록 anonymous를 True로 설정한다.
- 그리고 rospy.Subscriber로 String형태의 chatter라는 이름의 토픽을 가져온다. chatter라는 이름처럼 Subscriber가 받는 토픽의 이름은 publisher에서 만든 토픽의 이름과 동일해야한다. 그리고 토픽을 받는 순간 callback을 실행하여 터미널에 신호를 받았다는 신호가 출력된다.
이전에 수정했던 CMakeLists.txt에서 다음과 같이 추가로 수정한다.
catkin_install_python(PROGRAMS scripts/talker.py scripts/listener.py
DESTINATION ${CATKIN_PACKAGE_BIN_DESTINATION}
)
Publisher와 Subscriber를 모두 생성하였으므로 catkin_ws 디렉토리로 넘어가서 catkin_make로 빌드를 진행한다.
cd ~/catkin_ws
catkin_make
다음 각각 한줄을 터미널에 입력하여 publisher와 subscriber가 신호를 잘 주고받는지 확인한다.
roscore
rosrun beginner_tutorials talker.py
rosrun beginner_tutorials listener.py
왼쪽이 Publisher인 talker.py를 실행하였을 때의 터미널 결과이고, 오른쪽이 Publisher가 보낸 토픽을 받은 Subscriber가 터미널에 받았다는 신호를 보낸 것이다. 위와 같이 통신이 이루어지면 성공이다.
추가로 rqt를 이용하여 어떻게 통신이 이루어지고 있는지도 확인 가능하다.
rqt_graph
Publisher인 talker에서 chatter라는 이름의 토픽을 Subscriber인 listener에게 보내는 것을 그림으로 한번에 확인 가능하다.
이런식으로 서비스, 파라미터 통신에 대해서도 진행을 해본다. 따로 설명은 하지 않겠다.
http://wiki.ros.org/rospy_tutorials/Tutorials
위의 주소를 들어가서 2, 3번 튜토리얼을 따라하면 되겠다.
Numpy 사용
Numpy는 python에서 제공하는 라이브러리로 행렬 계산에 특화되어 있는 배열 형태를 제공한다.
다음의 의존성 패키지 선언을 package.xml에 적기만 하면 된다.
<build_depend>python-numpy</build_depend>
<run_depend>python-numpy</run_depend>
자세한 내용은 역시나 wikiROS를 참고한다.
http://wiki.ros.org/rospy_tutorials/Tutorials/numpy
roslaunch 사용법
- rosrun 명령어로는 하나의 노드만 실행할 수 있는데 roslaunch를 이용하면 하나 이상의 정해진 노드를 실행할 수 있다.
- 그 밖의 기능으로 노드를 실행할 때 패키지의 매개변수나 노드 이름 변경, 노드 네임스페이스 설정, ROS_ROOT 및 ROS_PACKAGE_PATH 설정, 환경 변수 변경 등의 옵션을 붙일 수 있는 ROS 명령어이다.
- roslaunch는 '*.launch'라는 파일을 사용하며 실행 노드를 설정하는데 이는 XML 기반이며, 태그별 옵션을 제공한다.
- 실행 명령어는 "roslaunch [패키지명] [roslaunch 파일]"이다.
*.launch 파일을 작성하기 위해 앞서 만든 패키지 폴더에 launch라는 폴더를 생성한다.
반드시 동시동작 시키려는 패키지 폴더 안에 넣어야 한다. 이후 union.launch라는 새로운 파일을 폴더 안에 생성한다.
우리는 union.launch에 우리가 실행하려는 노드들을 기술할 것이고 launch할 때 이 파일을 실행할 것이다.
roscd ros_tutorials_topic
mkdir launch
cd launch
gedit union.launch
그리고 union.launch 파일에 다음과 같이 작성한다.
<launch>
<group ns="ns1">
<node pkg="ros_tutorials_topic" type="talker.py" name="talker"/>
<node pkg="ros_tutorials_topic" type="listener.py" name="listener"/>
</group>
<group ns="ns2">
<node pkg="ros_tutorials_topic" type="talker.py" name="talker"/>
<node pkg="ros_tutorials_topic" type="listener.py" name="listener"/>
</group>
</launch>
- <lanuch> 태그 안에 사용할 노드들을 정의한다. <node> 태그 안에 우리가 사용할 패키지와 노드를 기술한다.
- pkg: 패키지의 이름
- type: 실제 실행할 노드의 이름(노드 명)
- name: 위 type에 해당하는 노드가 실행될 때 붙는 이름(실행명), 일반적으로 type과 동일하게 설정하지만 필요에 따라 이름을 달리 할 수 있다. 터미널에 rosnode list를 입력하면 node가 이 name에 맞춰 생성된다.
- 위에서 group을 나눈 이유는 같은 노드를 사용하여 전달되는 메시지가 겹치기 때문에 이를 해결하기 위해서 roslaunch 네임스페이스 태그를 이용하였다.
이러면 2개의 노드를 동시에 실행할 준비가 완료 되었다. 다음의 한줄로 roslaunch를 사용할 수 있다.
--screen 옵션은 터미널에 실행되는 모든 노드의 출력을 스크린에 표시해준다.
roslaunch ros_tutorials_topic union.launch --screen
ns1 그룹과 ns2 그룹에서 동시에 토픽을 주고받는 것을 확인할 수 있다.
rqt_graph로 토픽을 어떤 식으로 주고받는지 시각화할 수 있다.
노드의 그룹이 나뉘어 publisher가 각자의 subscriber로 토픽을 보냄을 확인할 수 있다.
roslaunch에서 사용할 수 있는 태그들은 다음과 같다. 사용하고 싶은 태그를 골라서 작성하면 되겠다.
- <launch> : roslaunch 구문의 시작과 끝을 가리킨다.
- <node> : 노드 실행에 대한 태그이며 패키지, 노드명, 실행명을 변경할 수 있다.
- <machine> : 노드를 실행하는 PC의 이름, address, ros-root, ros-package-path 등을 설정할 수 있다.
- <include> : 다른 패키지나 같은 패키지에 속해 있는 다른 launch를 불러와 하나의 launch 파일처럼 실행할 수 있다.
- <remap> : 노드 이름, 토픽 이름 등의 노드에서 사용 중인 ROS 변수의 이름을 변경할 수 있다.
- <env> : 경로, IP 등의 환경 변수를 설정한다. (거의 안쓰임)
- <param> : 매개변수 이름, 타이프, 값 등을 설정한다.
- <rosparam> : rosparam 명령어처럼 load, dump, delete 등 매개변수 정보의 확인 및 수정한다.
- <group> : 실행되는 노드를 그룹화할 때 사용
- <test> : 노드를 테스트할 때 사용한다. 테스트에 사용할 수 있는 옵션들이 추가되어 있다.
- <arg> : launch 파일 내에 변수를 정의할 수 있어서 launch할 때 매개변수를 변경 시킬 수 있다.
출처
- ROS 강의 Chapter7(표윤석) : https://www.youtube.com/watch?v=iGdQHi_wL1Y&list=PLRG6WP3c31_VIFtFAxSke2NG_DumVZPgw&index=7
- ROS Wiki : http://wiki.ros.org/rospy/Tutorials