[ROS2]024-025: ROS 프로그래밍 기초(Python/C++)

maroo·2022년 7월 23일
0

ROS2

목록 보기
14/39

https://cafe.naver.com/openrt/24450

1. ROS의 Hello World

보통 처음엔 Hello world를 출력해 본다.
ROS에서는 메시지를 전송해 보는 것이 그 역할이다.

2. 패키지 생성

ROS 2 패키지 생성 명령어는 다음과 같다.(참고로, 예전에 만들었던 workspace에서 실행해야 한다. 나의 경우 ros2_ws)
ros2 pkg create [패키지이름] --build-type [빌드 타입] --dependencies [의존하는패키지1][의존하는패키지n]

$ cd ~/ros2_ws/src/

Python

$ ros2 pkg create my_first_ros_rclpy_pkg --build-type ament_python --dependencies rclpy std_msgs

의존하는 패키지 1: rclpy(ROS에서 Python을 사용하기 위한 클라이언트 라이브러리)
의존하는 패키지 2: std_msgs(ROS의 표준 메시지 패키지)

C++

$ ros2 pkg create my_first_ros_rclcpp_pkg --build-type ament_cmake --dependencies rclcpp std_msgs

의존하는 패키지 1: rclcpp(ROS에서 C++를 사용하기 위한 클라이언트 라이브러리)
의존하는 패키지 2: std_msgs

3. 패키지 설정

3.1 패키지 설정 파일 (package.xml)
들어가기 전, .xml파일에 대한 간략히 알아보자(잘 모른다)
.xml파일은 엑셀, .json파일과 비슷한 개념이다. 데이터를 쉽게 구조화해 전달하기 위한 문서 종류이다.

<태그이름 속성1="" 속성2=""... >내용</태그이름>

과 같은 구조로 이루어진다.
주석을 달고 싶다면
(velog가 html문법을 따르기 때문에 저것도 주석 처리가 되어 표시가 안 돼서 사진으로 넣었다..)

Python

ros2_ws의 my_first_ros_rclpy_pkg에 들어가서 package.xml파일을 찾으면 나의 경우 다음과 같은 내용을 확인할 수 있었다.
(오로카 포스팅에서는 작성하라고 했는데, 이미 생성되어 있었다. 이름, 메일 주소 등 개인정보를 제외하고는 내 파일 내용과 거의 다르지 않아서 일단 바꾸지 않기로 했다)
7번 실행에서 에러가 나서, 결국 오로카 포스팅대로 바꾸었다.
원래 내 파일 내용

<?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_first_ros_rclpy_pkg</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="hcw@todo.todo">hcw</maintainer>
  <license>TODO: License declaration</license>

  <depend>rclpy</depend>
  <depend>std_msgs</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>
  </export>
</package>

오로카 포스팅 참고해서 수정

<?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_first_ros_rclpy_pkg</name>
  <version>0.0.2</version>
  <description>ROS 2 rclpy basic package for the ROS 2 seminar</description>
  <maintainer email="hcw@todo.todo">hcw</maintainer>
  <license>Apache License 2.0</license>
  <author email="mikael@osrfoundation.org">Mikael Arguedas</author>
  <author email="hcw@todo.todo">hcw</author>

  <depend>rclpy</depend>
  <depend>std_msgs</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>
  </export>
</package>

C++

원래 내 파일 내용

<?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_first_ros_rclcpp_pkg</name>
  <version>0.0.0</version>
  <description>TODO: Package description</description>
  <maintainer email="hcw@todo.todo">hcw</maintainer>
  <license>TODO: License declaration</license>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

오로카 포스팅 참고해서 수정

<?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_first_ros_rclcpp_pkg</name>
  <version>0.0.1</version>
  <description>ROS 2 rclcpp basic package for the ROS 2 seminar</description>
  <maintainer email="hcw@todo.todo">hcw</maintainer>
  <license>Apache License 2.0</license>
  <author>Mikael Arguedas</author>
  <author>Morgan Quigley</author>
  <author email="jacob@openrobotics.org">Jacob Perron</author>
  <author email="hcw@todo.todo">hcw</author>

  <buildtool_depend>ament_cmake</buildtool_depend>

  <depend>rclcpp</depend>
  <depend>std_msgs</depend>

  <test_depend>ament_lint_auto</test_depend>
  <test_depend>ament_lint_common</test_depend>

  <export>
    <build_type>ament_cmake</build_type>
  </export>
</package>

3.2 <파이썬> 패키지 설정 파일 (setup.py)
원래 내 파일 내용

from setuptools import setup

package_name = 'my_first_ros_rclpy_pkg'

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='hcw',
    maintainer_email='hcw@todo.todo',
    description='TODO: Package description',
    license='TODO: License declaration',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
        ],
    },
)

오로카 포스팅 참고해서 수정

from setuptools import find_packages
from setuptools import setup

package_name = 'my_first_ros_rclpy_pkg'

setup(
    name=package_name,
    ~~//버전은 package.xml과 같이 0.0.0으로 남겨두기로 했다.~~
    //아니..그냥 0.0.2로 바꾸기로 했다.
    version='0.0.2',
    packages=find_packages(exclude=['test']),
    data_files=[
        ('share/ament_index/resource_index/packages',
            ['resource/' + package_name]),
        ('share/' + package_name, ['package.xml']),
    ],
    install_requires=['setuptools'],
    zip_safe=True,
    //author부분은 일단 필요없어 보여 삭제하기로 했다.
    //author='Mikael Arguedas, Pyo',
    //author_email='mikael@osrfoundation.org, pyo@robotis.com',
    maintainer='hcw',
    maintainer_email='hcw@todo.todo',
    keywords=['ROS'],
    classifiers=[
        'Intended Audience :: Developers',
        'License :: OSI Approved :: Apache Software License',
        'Programming Language :: Python',
        'Topic :: Software Development',
    ],
    description='ROS 2 rclpy basic package for the ROS 2 seminar',
    ~~//license는 내 것으로 바꿔 주었다~~
    //아니..원래 것으로 바꿔라. 이게 오류의 원인이었던 듯
    license='Apache License, Version 2.0',
    tests_require=['pytest'],
    entry_points={
        'console_scripts': [
            'helloworld_publisher = my_first_ros_rclpy_pkg.helloworld_publisher:main',
            'helloworld_subscriber = my_first_ros_rclpy_pkg.helloworld_subscriber:main',
        ],
    },
)

뭔가 마음대로 바꾼 것 같은데 이게 어떤 문제를 초래할지는 모르겠다..
test package니까 이 정도 변동은 괜찮겠지.

음...역시 마음대로 하면 문제가 생긴다.

3.2 <C++> 빌드 설정 파일(CMakeLists.txt)
원래 내 파일 내용

cmake_minimum_required(VERSION 3.5)
project(my_first_ros_rclcpp_pkg)

# Default to C99
if(NOT CMAKE_C_STANDARD)
  set(CMAKE_C_STANDARD 99)
endif()

# Default to C++14
if(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 dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

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()

오로카 포스팅 참고해서 수정

# Set minimum required version of cmake, project name and compile options
cmake_minimum_required(VERSION 3.5)
project(my_first_ros_rclcpp_pkg)

if(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 dependencies
find_package(ament_cmake REQUIRED)
find_package(rclcpp REQUIRED)
find_package(std_msgs REQUIRED)

# Build
add_executable(helloworld_publisher src/helloworld_publisher.cpp)
ament_target_dependencies(helloworld_publisher rclcpp std_msgs)

add_executable(helloworld_subscriber src/helloworld_subscriber.cpp)
ament_target_dependencies(helloworld_subscriber rclcpp std_msgs)

# Install
install(TARGETS
  helloworld_publisher
  helloworld_subscriber
  DESTINATION lib/${PROJECT_NAME})

# Test
if(BUILD_TESTING)
  find_package(ament_lint_auto REQUIRED)
  ament_lint_auto_find_test_dependencies()
endif()

# Macro for ament package
ament_package()

3.3 <파이썬> 패키지 환경 설정 파일 (setup.cfg)
이 파일은 오로카 포스팅과 내 파일이 완전히 똑같다. 수정할 필요 없다.

[develop]
script-dir=$base/lib/my_first_ros_rclpy_pkg
[install]
install-scripts=$base/lib/my_first_ros_rclpy_pkg

4. 퍼블리셔 노드 작성

Python

~/ros2_ws/src/my_first_ros_rclpy_pkg/my_first_ros_rclpy_pkg/ 폴더에
helloworld_publisher.py라는 이름으로 소스 코드 파일을 직접 생성하여 넣어주자.
터미널에서 해당 폴더로 이동한 후 gedit helloworld_publisher.py를 통해 생성했다.

$ cd ~/ros2_ws/src/my_first_ros_rclpy_pkg/my_first_ros_rclpy_pkg/
$ gedit helloworld_publisher.py

파일 내용

import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from std_msgs.msg import String

class HelloworldPublisher(Node):

    def __init__(self):
        super().__init__('helloworld_publisher')
        qos_profile = QoSProfile(depth=10)
        self.helloworld_publisher = self.create_publisher(String, 'helloworld', qos_profile)
        self.timer = self.create_timer(1, self.publish_helloworld_msg)
        self.count = 0
        
    def publish_helloworld_msg(self):
        msg = String()
        msg.data = 'Hello World: {0}'.format(self.count)
        self.helloworld_publisher.publish(msg)
        self.get_logger().info('Published message: {0}'.format(msg.data))
        self.count += 1

def main(args=None):
    rclpy.init(args=args)
    node = HelloworldPublisher()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        node.get_logger().info('Keyboard Interrupt (SIGINT)')
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

나중에 포스팅에 있는 코드 설명을 보고 코드를 적는 것으로 복습을 해 보자.

C++

~/ros2_ws/src/my_first_ros_rclcpp_pkg/src/ 폴더에
helloworld_publisher.cpp라는 이름으로 소스 코드 파일을 직접 생성하여 넣어주자.

$ cd ~/ros2_ws/src/my_first_ros_rclcpp_pkg/src/
$ gedit helloworld_publisher.cpp

파일 내용

#include <chrono>
#include <functional>
#include <memory>
#include <string>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using namespace std::chrono_literals;


class HelloworldPublisher : public rclcpp::Node
{
public:
  HelloworldPublisher()
  : Node("helloworld_publisher"), count_(0)
  {
    auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
    helloworld_publisher_ = this->create_publisher<std_msgs::msg::String>(
      "helloworld", qos_profile);
    timer_ = this->create_wall_timer(
      1s, std::bind(&HelloworldPublisher::publish_helloworld_msg, this));
  }

private:
  void publish_helloworld_msg()
  {
    auto msg = std_msgs::msg::String();
    msg.data = "Hello World: " + std::to_string(count_++);
    RCLCPP_INFO(this->get_logger(), "Published message: '%s'", msg.data.c_str());
    helloworld_publisher_->publish(msg);
  }
  rclcpp::TimerBase::SharedPtr timer_;
  rclcpp::Publisher<std_msgs::msg::String>::SharedPtr helloworld_publisher_;
  size_t count_;
};


int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<HelloworldPublisher>();
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

5. 서브스크라이버 노드 작성

Python

$ cd ~/ros2_ws/src/my_first_ros_rclpy_pkg/my_first_ros_rclpy_pkg/
$ gedit helloworld_subscriber.py

파일 내용

import rclpy
from rclpy.node import Node
from rclpy.qos import QoSProfile
from std_msgs.msg import String

class HelloworldSubscriber(Node):

    def __init__(self):
        super().__init__('Helloworld_subscriber')
        qos_profile = QoSProfile(depth=10)
        self.helloworld_subscriber = self.create_subscription(
            String,
            'helloworld',
            self.subscribe_topic_message,
            qos_profile)

    def subscribe_topic_message(self, msg):
        self.get_logger().info('Received message: {0}'.format(msg.data))

def main(args=None):
    rclpy.init(args=args)
    node = HelloworldSubscriber()
    try:
        rclpy.spin(node)
    except KeyboardInterrupt:
        node.get_logger().info('Keyboard Interrupt (SIGINT)')
    finally:
        node.destroy_node()
        rclpy.shutdown()

if __name__ == '__main__':
    main()

C++

$ cd ~/ros2_ws/src/my_first_ros_rclcpp_pkg/src/
$ gedit helloworld_subscriber.cpp

파일 내용

#include <functional>
#include <memory>

#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"

using std::placeholders::_1;


class HelloworldSubscriber : public rclcpp::Node
{
public:
  HelloworldSubscriber()
  : Node("Helloworld_subscriber")
  {
    auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
    helloworld_subscriber_ = this->create_subscription<std_msgs::msg::String>(
      "helloworld",
      qos_profile,
      std::bind(&HelloworldSubscriber::subscribe_topic_message, this, _1));
  }

private:
  void subscribe_topic_message(const std_msgs::msg::String::SharedPtr msg) const
  {
    RCLCPP_INFO(this->get_logger(), "Received message: '%s'", msg->data.c_str());
  }
  rclcpp::Subscription<std_msgs::msg::String>::SharedPtr helloworld_subscriber_;
};


int main(int argc, char * argv[])
{
  rclcpp::init(argc, argv);
  auto node = std::make_shared<HelloworldSubscriber>();
  rclcpp::spin(node);
  rclcpp::shutdown();
  return 0;
}

6. 빌드

//workspace내 모든 패키지 빌드
$ cd ~/ros2_ws && colcon build --symlink-install

//특정 패키지 빌드
$ cd ~/ros2_ws && colcon build --symlink-install --packages-select [패키지 이름1] [패키지 이름2] [패키지 이름N]

//특정 패키지 및 의존성 패키지를 함께 빌드
$ cd ~/ros2_ws && colcon build --symlink-install --packages-up-to [패키지 이름]

Python

my_first_ros_rclpy_pkg 패키지만 빌드해 보자.
특정 패키지의 첫 빌드 때는 빌드 후 실행 가능한 패키지의 노드 설정들을 해줘야 빌드된 노드를 실행할 수 있다.

$ cd ~/ros2_ws
$ colcon build --symlink-install --packages-select my_first_ros_rclpy_pkg
$ . ~/ros2_ws/install/local_setup.bash


처음에 이런 에러가 나서, 3번으로 돌아가 파일 내용을 수정해 주었다.
그랬더니 잘 되었다.

C++

my_first_ros_rclcpp_pkg 패키지만 빌드해 보자.

$ cd ~/ros2_ws
$ colcon build --symlink-install --packages-select my_first_ros_rclcpp_pkg
$ . ~/ros2_ws/install/local_setup.bash

7. 실행

Python

$ ros2 run my_first_ros_rclpy_pkg helloworld_subscriber
$ ros2 run my_first_ros_rclpy_pkg helloworld_publisher

C++

$ ros2 run my_first_ros_rclcpp_pkg helloworld_subscriber
$ ros2 run my_first_ros_rclcpp_pkg helloworld_publisher

의문점

terminal에서 6, 7을 통한 빌드와 실행은 잘 되는데,
VScode에서 helloworld_subscriber.cpp 코드 실행을 해 보면 include에서부터 에러가 난다.

따배씨에서 했던 걸 생각해 보면 hpp파일이 같은 프로젝트 폴더 내에 있었던 것 같기는 하다.
같은 폴더 내에 저 hpp파일이 없어서 에러가 나는 건가.
나는 아직도 IDE라는 것을 잘 모르겠다..하핫
일단 실행은 그냥 terminal로 시켜보는 걸로 하고,
VScode는 코드 뷰와 주석 달기를 편리하게 하는 용도로 일단 사용하자^^
천리길도 한 걸음부터........

profile
할수이따 ~

0개의 댓글