04장 Build system
Last updated
Was this helpful?
Last updated
Was this helpful?
77Creating a launch file
Writing a simple publisher and subscriber (Python)
Writing a simple publisher and subscriber (C++)
발표자 : 권준호, 김효민참석자 : 최규남, 류정필, 이태용, 김효민,김경남,박정은,안지훈, 김진성, 이창환, Yu Minsang,SANGYONG, 최종우,정하정
ROS2를 사용하기전에 작업환경을 구축함.실행을 하기전에 ROS2가 설치 되어있어야 함.
기본자료
위 코드를 입력하게 되면 다음과 같이 ros2_ws이름의 디렉토리가가 생성 및 디렉토리 안으로 들어가는 것을 볼 수 있음
colcon 은 ROS를 위한 범용 빌드 툴로서 개발되고 있는 툴이다. 이 툴은 ROS1, ROS2, 비 ROS 소프트웨어까지 모두 포괄하여 사용할 수 있도록 설계되었고, 기존에 설명한 catkin_make, catkin_make_isolated, catkin_tools, ament_tools 의 모든 기능을 사용 할 수 있도록 개발되고 있다.
위의 코드를 입력 하게 되면 다음과 같이 나타나는것을 볼 수 있다
다만, 처음 colcon build를 입력하게 된다면 오류 메시지가 뜰 수 있다. 이 경우
$ sudo apt install python3-colcon-common-extensions
를 통해 해결할 수 있다.
ll (ls-l) 를 입력하여 현재 디렉토리의 자세한 내용을 알아보자
마지막으로 다음 자료를 다운 받으면 기초적인 개발환경 구축이 완료된다.
빌드에 앞서 다음 코드를 이용해 설치 한다.
먼저 dev_ws(development workspace) 라는 이름의 디렉토리를 생성후 디렉토리에 접속해보자.
샘플을 복제하기전 dev_ws/src에 들어가 있어야 함. dev_ws/src디렉토리에 들어가 있을 경우, 다음 명령어를 통해 ros_tutorials를 복제해 보자
이렇게 복제를 완료한 뒤에 ros_tutorials list를 확인해 보면 다음과 같이 나타나는 것을 볼 수 있다.
작업 공간을 샘플 패키지로 채웠으나 아직 완전히 작동하는 작업 공간은 아니다. 종속성을 해결하고 작업 공간을 먼저 구축해야 한다.
작업 공간을 구축하기 전 패키지 종속성을 해결해야 한다. 이미 모든 종속성을 가지고 있을 수 있지만, 최선의 방법은 복제할 때마다 종속성을 확인하는 것이다.
다음의 명령을 통해 종속을 해결할 수 있다.
다만 그 전에 rosdep가 설치되어있지 않다면 정상적으로 작동되지 않을것이다다음과 같이 ros dep 를 설치하자
이후 colcon build를 이용하여 명령을 내리면 다음과 같은 결과를 얻을 수 있다.
turtlesim 창의 제목 표시줄을 편집하여 오버레이에서 turtlesim을 수정할 수 있다. 다음의 디렉토리 내에서 turtle_frame.cpp텍스트를 수정해보자.
turtle_frame.cpp텍스트에 들어갔다면 setWindowTitle("TurtleSim") 의 ”TurtleSim” 을 ”MyTurtleSim” 로 수정하고 저장하자
이후 이전에 colcon build를 실행한 터미널 창으로 돌아가 colcon build를 재실행한다. 다음을 통해 주 ROS 2 환경을 "언더레이"로 제공하므로 오버레이를 "위에" 구축할 수 있다
그후 dev_ws디렉토리에서 오버레이를 소싱한다
노드를 실행할 때 마다 terminal을 새로 열어서 실행을 한다는 것은 귀찮은 일임.여러노드를 한번에 열수 있게 Launch file을 제공함.
위의 sim node가 동일해 보이지만 node_namespace로 구분되기 때문에 괜찮음.
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으로 설정하면 예와 동일하게 출력됨.
패키지는 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는 단순하고 의존성이 전혀 없지만 이 공간이 다가오는 튜토리얼에서 활용되고 있는 것을 볼 수 있을 것이다.
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
msg_type – The type of ROS messages the publisher will publish.
topic (str) – The name of the topic the publisher will publish to.
event_callbacks (Optional[PublisherEventCallbacks]) – User-defined callbacks for middleware events.
create_subscription(msg_type, topic, callback, qos_profile, *, callback_group=None, event_callbacks=None, raw=False)Create a new subscription.Parameters
msg_type – The type of ROS messages the subscription will subscribe to.
topic (str) – The name of the topic the subscription will subscribe to.
callback (Callable[[~MsgType], None]) – A user-defined callback function that is called when a message is received by the subscription.
event_callbacks (Optional[SubscriptionEventCallbacks]) – User-defined callbacks for middleware events.
raw (bool) – If True, then received messages will be stored in raw binary representation.
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
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
colcon 사용법 및 옵션들은 기존 툴들과 호환된다. 그 사용법에 대한 설명은 아래의 링크로 대신한다. 원론적으로 왜 이 범용 빌드 툴이 나오게 되었는지에 대한 고민과 의견 수렴에 대해서는 사용자 입장에서는 그렇게 크게 중요하지 않다고 생각한다. 우리에게 더 중요한 자세한 사용 방법은 이어지는 ROS2 실제 프로그래밍에서 더 깊숙히 파헤쳐 보도록 하자.
그 다음 터틀심을 실행하면 창의 이름이 바뀌어 있다는것을 확인할 수 있다.하지만 언더레이가 여전히 손상되지 않았는지 확인하려면 새 터미널을 열고 다시 실행해보면 원래대로 돌아오는것을 알 수 있다.
기본 자료 :
qos_profile (Union[, int]) – A QoSProfile or a history depth to apply to the publisher. In the case that a history depth is provided, the QoS history is set to RMW_QOS_POLICY_HISTORY_KEEP_LAST, the QoS history depth is set to the value of the parameter, and all other QoS settings are set to their default values.
callback_group (Optional[]) – The callback group for the publisher’s event handlers. If None, then the node’s default callback group is used.
Return typeReturnsThe new publisher.
qos_profile (Union[, int]) – A QoSProfile or a history depth to apply to the subscription. In the case that a history depth is provided, the QoS history is set to RMW_QOS_POLICY_HISTORY_KEEP_LAST, the QoS history depth is set to the value of the parameter, and all other QoS settings are set to their default values.
callback_group (Optional[]) – The callback group for the subscription. If None, then the nodes default callback group is used.
Return type
node () – A node to add to the executor to check for work.
executor () – The executor to use, or the global executor if None.
전체 흐름은 python과 동일함. $ ros2 pkg create --build-type ament_cmake cpp_pubsub $ wget -O publisher_member_function.cpp $ wget -O subscriber_member_function.cpp $ tree.├── CMakeLists.txt├── include│ └── cpp_pubsub├── package.xml└── src ├── publisher_member_function.cpp └── subscriber_member_function.cpp : rclcpp provides the standard C++ API for interacting with ROS 2.
mimic을 수정하여 입력을 반전시켜 동작하게 하고, 이를 lunch file로 구성하여 turtle sim 동작으로 보자.* 출력창은 1개만 뜨게 할 것. 정하정turtle_teleop_key node를 launch 파일에서 실행했더니 키보드 입력시 문자열로 추가되네요.ㅠㅠ .