04장 Build system
77Creating a launch file
https://index.ros.org/doc/ros2/Tutorials/Launch-Files/Creating-Launch-Files/#ros2launch
Writing a simple publisher and subscriber (Python)
https://index.ros.org/doc/ros2/Tutorials/Writing-A-Simple-Py-Publisher-And-Subscriber/#pypubsub
Writing a simple publisher and subscriber (C++)
https://index.ros.org/doc/ros2/Tutorials/Writing-A-Simple-Cpp-Publisher-And-Subscriber/#cpppubsub 발표자 : 권준호, 김효민참석자 : 최규남, 류정필, 이태용, 김효민,김경남,박정은,안지훈, 김진성, 이창환, Yu Minsang,SANGYONG, 최종우,정하정
+ Build system 1-1
ROS2를 사용하기전에 작업환경을 구축함.실행을 하기전에 ROS2가 설치 되어있어야 함.
기본자료
https://www.theconstructsim.com/ros2-in-5-mins-007-how-to-create-a-ros2-overlay-workspace/ https://index.ros.org/doc/ros2/Tutorials/Workspace/Creating-A-Workspace/#ros2workspace
디렉토리생성
위 코드를 입력하게 되면 다음과 같이 ros2_ws이름의 디렉토리가가 생성 및 디렉토리 안으로 들어가는 것을 볼 수 있음
colcon에 대하여
colcon 은 ROS를 위한 범용 빌드 툴로서 개발되고 있는 툴이다. 이 툴은 ROS1, ROS2, 비 ROS 소프트웨어까지 모두 포괄하여 사용할 수 있도록 설계되었고, 기존에 설명한 catkin_make, catkin_make_isolated, catkin_tools, ament_tools 의 모든 기능을 사용 할 수 있도록 개발되고 있다.
colcon사용법
colcon 사용법 및 옵션들은 기존 툴들과 호환된다. 그 사용법에 대한 설명은 아래의 링크로 대신한다. 원론적으로 왜 이 범용 빌드 툴이 나오게 되었는지에 대한 고민과 의견 수렴에 대해서는 사용자 입장에서는 그렇게 크게 중요하지 않다고 생각한다. 우리에게 더 중요한 자세한 사용 방법은 이어지는 ROS2 실제 프로그래밍에서 더 깊숙히 파헤쳐 보도록 하자. https://cafe.naver.com/openrt/18732 http://design.ros2.org/articles/build_tool.html http://colcon.readthedocs.io/ https://github.com/colcon https://github.com/ros2/ros2/wiki/Colcon-Tutorial https://colcon.readthedocs.io/en/latest/migration/ament_tools.html https://colcon.readthedocs.io/en/latest/migration/catkin_make_isolated.html https://colcon.readthedocs.io/en/latest/migration/catkin_tools.html
위의 코드를 입력 하게 되면 다음과 같이 나타나는것을 볼 수 있다
다만, 처음 colcon build를 입력하게 된다면 오류 메시지가 뜰 수 있다. 이 경우
$ sudo apt install python3-colcon-common-extensions
를 통해 해결할 수 있다.
ll (ls-l) 를 입력하여 현재 디렉토리의 자세한 내용을 알아보자
마지막으로 다음 자료를 다운 받으면 기초적인 개발환경 구축이 완료된다.
+ Build system 1-2
빌드에 앞서 다음 코드를 이용해 설치 한다.
디렉토리 생성
먼저 dev_ws(development workspace) 라는 이름의 디렉토리를 생성후 디렉토리에 접속해보자.
샘플복제
샘플을 복제하기전 dev_ws/src에 들어가 있어야 함. dev_ws/src디렉토리에 들어가 있을 경우, 다음 명령어를 통해 ros_tutorials를 복제해 보자
이렇게 복제를 완료한 뒤에 ros_tutorials list를 확인해 보면 다음과 같이 나타나는 것을 볼 수 있다.
작업 공간을 샘플 패키지로 채웠으나 아직 완전히 작동하는 작업 공간은 아니다. 종속성을 해결하고 작업 공간을 먼저 구축해야 한다.
패키지 종속 해결
작업 공간을 구축하기 전 패키지 종속성을 해결해야 한다. 이미 모든 종속성을 가지고 있을 수 있지만, 최선의 방법은 복제할 때마다 종속성을 확인하는 것이다.
다음의 명령을 통해 종속을 해결할 수 있다.
다만 그 전에 rosdep가 설치되어있지 않다면 정상적으로 작동되지 않을것이다다음과 같이 ros dep 를 설치하자
colcon build를 통해 작업환경 구축
이후 colcon build를 이용하여 명령을 내리면 다음과 같은 결과를 얻을 수 있다.
Overlay 수정
turtlesim 창의 제목 표시줄을 편집하여 오버레이에서 turtlesim을 수정할 수 있다. 다음의 디렉토리 내에서 turtle_frame.cpp텍스트를 수정해보자.
turtle_frame.cpp텍스트에 들어갔다면 setWindowTitle("TurtleSim") 의 ”TurtleSim” 을 ”MyTurtleSim” 로 수정하고 저장하자
이후 이전에 colcon build를 실행한 터미널 창으로 돌아가 colcon build를 재실행한다. 다음을 통해 주 ROS 2 환경을 "언더레이"로 제공하므로 오버레이를 "위에" 구축할 수 있다
그후 dev_ws디렉토리에서 오버레이를 소싱한다
+ Creating a launch file
노드를 실행할 때 마다 terminal을 새로 열어서 실행을 한다는 것은 귀찮은 일임.여러노드를 한번에 열수 있게 Launch file을 제공함.
turtlesim_mimic_launch.py
위의 sim node가 동일해 보이지만 node_namespace로 구분되기 때문에 괜찮음.
https://github.com/ros/ros_tutorials/blob/melodic-devel/turtlesim/tutorials/mimic.cpp
mimic node의 경우 remapping을 사용하는 것을 살펴보면,mimic의 /input/pose topic을 /turtlesim1/turtle1/pose로 /output/cmd_vel topic을 /turtlesim2/turtle1/cmd_vel로 재설정 함.
결국 /turtlesim1/turtle1/pose(위치)가 mimic의 /input/pose 입력으로 들어가고 mimic의 출력 /output/cmd_vel(속도)이 /turtlesim2/turtle1/cmd_vel로 연결된다.
* 실행시 turtlesim이 설치되어 있어야 동작 되는 것으로 보임.
rqt_graph로 현재 node의 관계를 좀더 직관적으로 살펴볼수 있다.
실제 동작을 확인해 보자.
rqt_graph에 Group을 0으로 설정하면 예와 동일하게 출력됨.
+ Creating a ROS 2 package
기본 자료 : https://index.ros.org/doc/ros2/Tutorials/Creating-A-ROS2-Package/#createpkg
패키지에 대하여
패키지는 ROS 2 코드를 위한 컨테이너라고 생각할 수 있다. 만약 코드를 설치하거나 다른 사람들과 공유할 수 있기를 원한다면, 그것을 패키지로 정리해야 한다. 이것으로, 자신의 작품을 공유할 수 있고 다른 사람들이 그것을 쉽게 만들고 사용할 수 있게 할 수 있다. ROS 2의 패키지 생성은 ament를 빌드 시스템으로, colcon을 빌드 도구로 사용한다. 다른 빌드 유형이 존재하나 공식적으로 지원되는 CMake 또는 Python을 사용하여 패키지를 생성할 수 있다.
패키지 생성
다음과 같이 dev_ws/src 디렉토리 내에서 패키지를 생성한다
이 튜토리얼의 경우 패키지에 간단한 Hello World 유형 실행 파일을 생성하는 선택적 인수 --node-name을 사용한다. 다음의 명령어를 통해 본 메시지를 받게된다.이 를통해 새 패키지에 대해 자동으로 생성된 파일을 볼 수 있다
패키지 구축
작업영역에 패키지를 넣는 것은 작업영역 루트에서 콜콘 빌드를 실행하여 한 번에 많은 패키지를 만들 수 있기 때문에 특히 중요하다. 그렇지 않으면 각 패키지를 개별적으로 만들어야 할 것이다.
다음 번에 my_package 패키지만 구축하려면 다음을 실행하십시오.
그런 다음 dev_ws 디렉터리 내부에서 다음 명령을 실행하여 작업 공간을 소싱한다.
이제 작업 공간이 경로에 추가되었으므로 새 패키지의 실행 파일을 사용할 수 있다. 패키지 생성 중 --node-name 인수를 사용하여 생성한 실행 파일을 실행하려면 다음 명령을 입력한다.
그결과 다음과 같은 메시지를 받게 된다. dev_ws/src/my_package 내부에는 자동으로 생성된 2pkg의 파일과 폴더가 표시된다.
내용중 7번 라인이자동으로 채워지지 않은 경우 7번 라인에 이름과 이메일을 입력하십시오. 그런 다음 라인 6에서 다음 설명을 편집하여 패키지를 요약한다.
그다음, 다음과 같이 8번라인의 라이센스를 업데이트한다
라이센스 태그 아래에 _depended로 끝나는 태그 이름이 있다. 여기가 package.xml이 colcon을 검색할 다른 패키지에 대한 의존성을 나열할 것이다. my_package는 단순하고 의존성이 전혀 없지만 이 공간이 다가오는 튜토리얼에서 활용되고 있는 것을 볼 수 있을 것이다.
+ Writing a simple publisher and subscriber (Python)
topic으로 string message를 처리하는 node를 작성한다.
rclpy |
http://docs.ros2.org/eloquent/api/rclpy/ |
create_publisher(msg_type, topic, qos_profile, *, callback_group=None, event_callbacks=None)Create a new publisher.Parameters
Return typePublisherReturnsThe new publisher. |
create_subscription(msg_type, topic, callback, qos_profile, *, callback_group=None, event_callbacks=None, raw=False)Create a new subscription.Parameters
Return typeSubscription |
rclpy.spin(node, executor=None)Execute work and block until the context associated with the executor is shutdown.Callbacks will be executed by the provided executor.This function blocks.Parameters
Return typeNone |
publish(msg)Send a message to the topic for the publisher.Parametersmsg (~MsgType) – The ROS message to publish.RaisesTypeError if the type of the passed message isn’t an instance of the provided type when the publisher was constructed.Return typeNone |
publisher_member_function.py |
Column 1 |
import rclpyfrom rclpy.node import Node from std_msgs.msg import String class MinimalPublisher(Node):=> Node를 상속받아서 새로운 class를 생성 def __init__(self): super().__init__('minimal_publisher') => 부모 class에게 현재 Node name 전달 self.publisher_ = self.create_publisher(String, 'topic', 10) => node publisher가 Message type String을 사용하고 ‘topic’ 이라는 topic name을 사용하며 queue의 크기 10 으로 정의함. timer_period = 0.5 # seconds self.timer = self.create_timer(timer_period, self.timer_callback) => 0.5초 마다 callback 함수를 호출하도록 timer 설정 self.i = 0 def timer_callback(self): msg = String() msg.data = 'Hello World: %d' % self.i => message 정의 self.publisher_.publish(msg) => message 전송 self.get_logger().info('Publishing: "%s"' % msg.data) => console에 출력 self.i += 1 def main(args=None): rclpy.init(args=args) minimal_publisher = MinimalPublisher() rclpy.spin(minimal_publisher) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) minimal_publisher.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() |
Column 1 |
import rclpyfrom rclpy.node import Node from std_msgs.msg import String class MinimalSubscriber(Node):=> Node를 상속받아서 새로운 class를 생성 def __init__(self): super().__init__('minimal_subscriber') self.subscription = self.create_subscription( String, 'topic', self.listener_callback, 10) => create_publish()와의 차이점은 message 수신시 대응할 callback 함수만 연결해 주면 된다. self.subscription # prevent unused variable warning def listener_callback(self, msg): self.get_logger().info('I heard: "%s"' % msg.data) => 수신한 message를 console에 출력함. def main(args=None): rclpy.init(args=args) minimal_subscriber = MinimalSubscriber() rclpy.spin(minimal_subscriber) # Destroy the node explicitly # (optional - otherwise it will be done automatically # when the garbage collector destroys the node object) minimal_subscriber.destroy_node() rclpy.shutdown() if __name__ == '__main__': main() |
Column 1 |
<?xml version="1.0"?><?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?><package format="3"> <name>py_pubsub</name> <version>0.0.0</version> <description>Examples of minimal publisher /subscriber using rclpy</description> <maintainer email="mrthinks@gmail.com">mrthink</maintainer> <license>Apache License 2.0</license> <buildtool_depend>ament_python</buildtool_depend> <test_depend>ament_copyright</test_depend> <test_depend>ament_flake8</test_depend> <test_depend>ament_pep257</test_depend> <test_depend>python3-pytest</test_depend> <export> <build_type>ament_python</build_type> <exec_depend>rclpy</exec_depend> <exec_depend>std_msgs</exec_depend> </export></package> |
수정 사항중 dependency부부은 python code에서 import하는 python package를 추가하면 된다.
Column 1 |
from setuptools import setup package_name = 'py_pubsub' setup( name=package_name, version='0.0.0', packages=[package_name], data_files=[ ('share/ament_index/resource_index/packages', ['resource/' + package_name]), ('share/' + package_name, ['package.xml']), ], install_requires=['setuptools'], zip_safe=True, maintainer='mrthink', maintainer_email='mrthinks@gmail.com', description='Examples of minimal publisher/subscriber using rclpy', license='Apache License 2.0', tests_require=['pytest'], entry_points={ 'console_scripts': [ 'talker = py_pubsub.publisher_member_function:main', 'listener = py_pubsub.subscriber_member_function:main', ], },) |
Column 1 |
setup.cfg는 수정없이 사용. setuptool에게 생성한 실행결과물이 lib 디렉토리에 있다고 알려줌. 새로운 package build를 하자.$ colcon build --packages-select py_pubsub 이제 두 node의 동작을 확인하자.$ . install/setup.bash$ ros2 run py_pubsub talker $ . install/setup.bash$ ros2 run py_pubsub listener
+ Writing a simple publisher and subscriber (C++)
전체 흐름은 python과 동일함. $ ros2 pkg create --build-type ament_cmake cpp_pubsub $ wget -O publisher_member_function.cpp https://raw.githubusercontent.com/ros2/examples/master/rclcpp/minimal_publisher/member_function.cpp $ wget -O subscriber_member_function.cpp https://raw.githubusercontent.com/ros2/examples/master/rclcpp/minimal_subscriber/member_function.cpp $ tree.├── CMakeLists.txt├── include│ └── cpp_pubsub├── package.xml└── src ├── publisher_member_function.cpp └── subscriber_member_function.cpp https://github.com/ros2/rclcpp: rclcpp provides the standard C++ API for interacting with ROS 2.
Column 1 |
#include <chrono>#include <memory>#include "rclcpp/rclcpp.hpp"#include "std_msgs/msg/string.hpp"using namespace std::chrono_literals;/* This example creates a subclass of Node and uses std::bind() to register a * member function as a callback from the timer. */class MinimalPublisher : public rclcpp::Node{public: MinimalPublisher() : Node("minimal_publisher"), count_(0) => count_를 0으로 초기화 { publisher_ = this->create_publisher<std_msgs::msg::String>("topic", 10); => Message type으로 String을 사용하고 topic 이름을 "topic"으로 정하고 queue 크기를 10으로 설정함. timer_ = this->create_wall_timer( 500ms, std::bind(&MinimalPublisher::timer_callback, this)); => 0.5초마다 callback 함수를 호출하도록 설정함. }private: void timer_callback() { auto message = std_msgs::msg::String(); message.data = "Hello, world! " + std::to_string(count_++); => message 생성. RCLCPP_INFO(this->get_logger(), "Publishing: '%s'", message.data.c_str()); => console에 내용 출력. publisher_->publish(message); => Message 발송. } rclcpp::TimerBase::SharedPtr timer_; rclcpp::Publisher<std_msgs::msg::String>::SharedPtr publisher_; size_t count_;};int main(int argc, char * argv[]){ rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<MinimalPublisher>()); rclcpp::shutdown(); return 0;} |
Column 1 |
#include <memory> #include "rclcpp/rclcpp.hpp" #include "std_msgs/msg/string.hpp" using std::placeholders::_1; class MinimalSubscriber : public rclcpp::Node { public: MinimalSubscriber() : Node("minimal_subscriber") { subscription_ = this->create_subscription<std_msgs::msg::String>( "topic", 10, std::bind(&MinimalSubscriber::topic_callback, this, _1)); } private: void topic_callback(const std_msgs::msg::String::SharedPtr msg) const { RCLCPP_INFO(this->get_logger(), "I heard: '%s'", msg->data.c_str()); } rclcpp::Subscription<std_msgs::msg::String>::SharedPtr subscription_; }; int main(int argc, char * argv[]) { rclcpp::init(argc, argv); rclcpp::spin(std::make_shared<MinimalSubscriber>()); rclcpp::shutdown(); return 0; } |
Column 1 |
<?xml version="1.0"?><?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?><package format="3"> <name>my_package2</name> <version>0.0.0</version> <description>Beginner developer tutorials practice package</description> <maintainer email="mrthinks@gmail.com">mrthink</maintainer> <license>Apache License 2.0</license> <buildtool_depend>ament_cmake</buildtool_depend> <test_depend>ament_lint_auto</test_depend> <test_depend>ament_lint_common</test_depend> <export> <build_type>ament_cmake</build_type> <exec_depend>rclcpp</exec_depend> <exec_depend>std_msgs</exec_depend> </export></package> |
Column 1 |
cmake_minimum_required(VERSION 3.5)project(cpp_pubsub)# Default to C99if(NOT CMAKE_C_STANDARD) set(CMAKE_C_STANDARD 99)endif()# Default to C++14if(NOT CMAKE_CXX_STANDARD) set(CMAKE_CXX_STANDARD 14)endif()if(CMAKE_COMPILER_IS_GNUCXX OR CMAKE_CXX_COMPILER_ID MATCHES "Clang") add_compile_options(-Wall -Wextra -Wpedantic)endif()# find dependenciesfind_package(ament_cmake REQUIRED)find_package(rclcpp REQUIRED)find_package(std_msgs REQUIRED)# uncomment the following section in order to fill in# further dependencies manually.# find_package(<dependency> REQUIRED)add_executable(talker src/publisher_member_function.cpp)ament_target_dependencies(talker rclcpp std_msgs)add_executable(listener src/subscriber_member_function.cpp)ament_target_dependencies(listener rclcpp std_msgs)install(TARGETS talker listener DESTINATION lib/${PROJECT_NAME})if(BUILD_TESTING) find_package(ament_lint_auto REQUIRED) # the following line skips the linter which checks for copyrights # uncomment the line when a copyright and license is not present in all source files #set(ament_cmake_copyright_FOUND TRUE) # the following line skips cpplint (only works in a git repo) # uncomment the line when this package is not in a git repo #set(ament_cmake_cpplint_FOUND TRUE) ament_lint_auto_find_test_dependencies()endif()ament_package() |
$ sudo rosdep install -i --from-path src --rosdistro dashing -y $ colcon build --packages-select cpp_pubsub $ . install/setup.bash$ ros2 run cpp_pubsub listener $ . install/setup.bash$ ros2 run cpp_pubsub talker ls
+ 미션
Last updated