ROS2 프로그래밍 규칙 (코드 스타일)
협업 프로그래밍 작업 시 일관된 규칙을 만들고, 이를 준수하여야 코드가 꼬이거나 문제가 생기지 않는다.
ROS가 지원하느 프로그래밍 언어는 다양하지만, 가장 많이 사용되는 C++과 python만 살펴보자.
C++ style
ROS2 Developer Guide 및 ROS2 Code Style에서 다루는 C++ 코드 스타일은 오픈소스 커뮤니티에서 가장 많이 사용되는 Google C++ Style Guide를 사용한다.
- 이름 규칙
- 타입, 클래스, 구조체, 열거형 : CamelCased
- 파일, 패키지, 인터페이스, 네임스페이스, 변수, 함수, 메소드 : snake_case
- 상수, 매크로 : ALL_CAPITALS
- 전역 변수는
g_
접두어를 붙인다. - 클래스 멤버 변수는 마지막에
_
를 추가한다.
- 공백 : 기본 들여쓰기는 space 2개를 사용한다.
- 함수, if, for 등의 괄호는 아래 줄로 넘겨서 시작한다.
1
2
3
4
if (a == 0)
{
a = 1;
}
python style
ROS2 Developer Guide 및 ROS2 Code Style에서 다루는 python 코드 스타일은 pythib
- 이름 규칙
- 타입, 클래스 : CamelCased
- 파일, 패키지, 인터페이스, 변수, 함수, 메소드 : snake_case
- 상수 : ALL_CAPITALS
- 공백 : 기본 들여쓰기는 space 4개를 사용한다.
ROS2 프로그래밍 기초 - python
ROS2에는 python에서 작업할 수 있도록 도와주는 rclpy가 있다.
패키지 생성
ROS2 패키지 생성 명령어는 다음과 같다.
ros2 pkg create [패키지이름] --build-type [빌드 타입] --dependencies [의존하는패키지1] [의존하는패키지2]..
1
2
cd ~/robot_ws/src/
ros2 pkg create rclpy_tutorial --build-type ament_python --dependencies rclpy std_msgs
이를 실행하고 나면 디렉토리는 다음과 같게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
src
└── rclpy_tutorial
├── rclpy_tutorial
└── \__init__.py
├── resource
└── rclpy_tutorial
├── test
└── test_
├── test_copyright.py
├── test_flake8.py
└── test_pep257.py
├── package.xml
├── setup.cfg
└── setup.py
이렇게 기본적으로 생성된 파일은 패키지 설정 파일인 package.xml, 파이썬 패키지 설정 파일인 setup.py, 파이썬 패키지 환경 설정 파일인 setup.cfg로 구성된다.
- package.xml
패키지 설정 파일은 사용할 RCL(ROS2 Client Libraries)에 따라 달라지는데, C++은 build type으로 ament_cmake가 사용되고, python은 ament_python으로 설정한다. 그 외에는 각기 다른 개발 환경에 맞춘 의존성 패키지를 설정해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<!-- package.xml -->
<?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>rclpy_tutorial</name>
<version>0.0.0</version>
<description>TODO: Package description</description>
<maintainer email="jhyoon@todo.todo">jhyoon</maintainer>
<license>TODO: License declaration</license>
<!-- packages -->
<depend>rclpy</depend>
<depend>std_msgs</depend>
<!-- dependencies -->
<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>
- setup.py
기본적으로 구성되어 있는 코드는 아래와 같다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
from setuptools import setup
package_name = 'rclpy_tutorial'
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='jhyoon',
maintainer_email='jhyoon@todo.todo',
description='TODO: Package description',
license='TODO: License declaration',
tests_require=['pytest'],
entry_points={
'console_scripts': [
],
},
)
파이썬 패키지 설정 파일은 entry_points 옵션의 console_scripts를 사용하는 부분이 중요하다. 이 부분에서 helloworld_publisher와 helloworld_subscriber 콘솔 스크립트는 각각 rclpy_tutorial.heeloworld_publisher 모듈과 rclpy_tutorial.helloworld_subscriber 모듈의 main 함수가 호출되게 된다. 이를 통해 ros2 run 또는 ros2 launch 로 해당 스크립트를 실행시킬 수 있다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
from setuptools import find_packages
from setuptools import setup
package_name = 'rclpy_tutorial'
setup(
name=package_name,
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='Mikael Arguedas, Pyo',
author_email='mikael@osrfoundation.org, pyo@robotis.com',
maintainer='Pyo',
maintainer_email='pyo@robotis.com',
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='Apache License, Version 2.0',
tests_require=['pytest'],
entry_points={
'console_scripts': [
'helloworld_publisher = rclpy_tutorial.helloworld_publisher:main',
'helloworld_subscriber = rclpy_tutorial.helloworld_subscriber:main',
],
},
)
- setup.cfg
파이썬 패키지 환경 설정 파일에서는 자신의 패키지 이름이 그대로 작성되어 있는지 확인해야 하며, 추후 colcon을 이용하여 빌드하게 되면 /home/<username>/robot_ws/install/<package-name>/lib/<package-name>
에 실행 파일이 생성된다.
[develop]
script-dir=$base/lib/rclpy_tutorial
[install]
install-scripts=$base/lib/rclpy_tutorial
노드 생성
- 퍼블리셔 노드 생성
퍼블리션 노드 소스 코드 파일은 robot_ws/src/rclpy_tutorial/rclpy_tutorial/
위치에 helloworld_publisher.py 파일을 생성한다. 아래의 코드는 helloworld 메시지를 보내주기 위한 코드이다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
#!/usr/bin/env python
# -*- coding: utf8 -*-
import rclpy
from rclpy.node import Node
# 퍼블리셔의 QoS 설정을 위해 QoSProfile 클래스를 사용
from rclpy.qos import QoSProfile
# 퍼블리싱하는 메시지 타입은 std_msgs.msg의 String이므로 import
from std_msgs.msg import String
# rclpy의 Node 클래스를 상속하여 사용
class HelloworldPublisher(Node):
def __init__(self):
# Node 클래스의 이름을 helloworld_publisher라 지정
super().__init__('helloworld_publisher')
# 퍼블리시할 데이터 버퍼에 10개까지 저장
qos_profile = QoSProfile(depth=10)
# 퍼블리셔 노드 생성 (msg type, topic name, QoS)
self.helloworld_publisher = self.create_publisher(String, 'helloworld', qos_profile)
# 콜백 함수 실행 시 사용되는 타이머로 지정한 값마다 콜백함수를 실행 (timer_period_sec, 발행을 실행할 함수)
self.timer = self.create_timer(1, self.publish_helloworld_msg)
self.count = 0
# callback function, callback함수를 구현할 때는 멤버함수, lambda, 지역 함수 등으로 선언이 가능하다. 이 때는 멤버함수 사용
def publish_helloworld_msg(self):
# 메시지 타입 - String
msg = String()
# 메시지의 data 입력
msg.data = 'Hello World: {0}'.format(self.count)
# 메시지 발행
self.helloworld_publisher.publish(msg)
# print와 비슷한 함수로 기록용
self.get_logger().info('Published message: {0}'.format(msg.data))
self.count += 1
def main(args=None):
# 초기화
rclpy.init(args=args)
# node라는 이름으로 클래스 생성
node = HelloworldPublisher()
try:
# 노드를 spin, 즉 지정된 콜백함수를 실행
rclpy.spin(node)
except KeyboardInterrupt:
# ctrl+c와 같은 인터럽트 시그널을 받으면 반복을 끝냄
node.get_logger().info('Keyboard Interrupt (SIGINT)')
finally:
# 노드 소멸
node.destroy_node()
# 노드 종료
rclpy.shutdown()
if __name__ == '__main__':
main()
- 서브스크라이버 노드 작성
퍼블리셔 노드와 같은 디렉토리인 /home/<username>/robot_ws/install/<package-name>/lib/<package-name>
에 helloworld_subscriber.py 파일을 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
#!/usr/bin/env python
# -*- coding: utf8 -*-
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')
# 서브스크라이버 데이터를 버퍼에 10개까지 저장
qos_profile = QoSProfile(depth=10)
self.helloworld_subscriber = self.create_subscription(
String,
'helloworld',
self.subscribe_topic_message,
qos_profile) # 메시지 타입, 토픽 이름, 콜백 함수, QoS
def subscribe_topic_message(self, msg):
# 데이터를 받으면 logging
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()
서브스크라이버의 코드는 퍼블리셔와 거의 동일하다.
빌드
ROS2에서는 colcon으로 빌드를 한다. 우선 소스 코드가 있는 workspace로 이동하고 colcon build 명령어를 통해 전체를 빌드한다. 여기에 빌드 옵션을 추가할 수 있는데, 특정 패키지만 선택하여 빌드하고자 할 때는 --packages-select
, symlink를 사용하려면 --symlink-install
옵션을 사용한다.
1
2
3
4
5
6
7
8
(워크스페이스내의 모든 패키지 빌드하는 방법)
$ cd ~/robot_ws && colcon build --symlink-install
(특정 패키지만 빌드하는 방법)
$ cd ~/robot_ws && colcon build --symlink-install --packages-select [패키지 이름1] [패키지 이름2] [패키지 이름N]
(특정 패키지 및 의존성 패키지를 함께 빌드하는 방법)
$ cd ~/robot_ws && colcon build --symlink-install --packages-up-to [패키지 이름]
따라서 위에 생성한 패키지만 빌드한다.
1
2
3
4
5
6
$ cd ~/robot_ws
$ colcon build --symlink-install --packages-select rclpy_tutorial
Starting >>> rclpy_tutorial
Finished <<< rclpy_tutorial [0.81s]
Summary: 1 package finished [1.00s]
특정 패키지의 첫 빌드 시에는 빌드 후 아래 명령어를 통해 환경 설정 파일을 불러와서 실행 가능한 패키지의 노드 설정들을 해줘야 빌드된 노드를 실행할 수 있다.
1
$ . ~/robot_ws/install/local_setup.bash
실행
각 노드를 실행할 때는 ros2 run
명령어를 통해 실행한다. 패키지 전체를 실행할 때는 ros2 launch
명령어를 사용할 수 있지만, 아직 launch를 생성하지 않았기에 우선 run으로 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ ros2 run rclpy_tutorial helloworld_subscriber
[INFO]: Received message: Hello World: 0
[INFO]: Received message: Hello World: 1
[INFO]: Received message: Hello World: 2
[INFO]: Received message: Hello World: 3
[INFO]: Received message: Hello World: 4
...
$ ros2 run rclpy_tutorial helloworld_publisher
[INFO]: Published message: Hello World: 0
[INFO]: Published message: Hello World: 1
[INFO]: Published message: Hello World: 2
[INFO]: Published message: Hello World: 3
[INFO]: Published message: Hello World: 4
...
1
$ rqt_graph
- rqt_graph
ROS2 프로그래밍 기초 - C++
ROS2에는 python과 마찬가지로 c++을 위해 제작된 rclcpp가 있다. 코드도 앞서 배운 python과 거의 동일하다.
패키지 생성
1
2
$ cd ~/robot_ws/src/
$ ros2 pkg create rclcpp_tutorial --build-type ament_cmake --dependencies rclcpp std_msgs
ROS에서 C++을 사용하기 위한 클라이언트 라이브러리 rclcpp를 사용한다. 이렇게 하면 ROS1과 동일한 디렉토리 경로를 확인할 수 있다.
1
2
3
4
5
6
7
src
└── rclpy_tutorial
├── include
└── rclcpp_tutorial
├── src
├── CMakeLists.txt
└── package.xml
생성된 package.xml, CMakeLists.txt은 각각 패키지 설정 파일과 빌드 설정 파일이다.
- package.xml
C++이라면 build type으로 ament_cmake를 사용한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<?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>rclcpp_tutorial</name>
<version>0.0.1</version>
<description>ROS 2 rclcpp basic package for the ROS 2 seminar</description>
<maintainer email="jhyoon@todo.todo">jhyoon</maintainer>
<license>Apache License 2.0</license>
<buildtool_depend>ament_cmake</buildtool_depend>
<!-- packages -->
<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>
- CMakeLists.txt
CMakeLists파일을 아래와 같이 수정한다.
cmake_minimum_required(VERSION 3.5)
project(rclcpp_tutorial)
# 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)
# 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})
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()
# Macro for ament package
ament_package()
노드 생성
- 퍼블리셔 노드 생성
퍼블리셔 노드는 ~/robot_ws/src/rclcpp_tutorial/src/
폴더에 helloworld_publisher.cpp 파일을 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
#include <chrono>
#include <functional>
#include <memory>
#include <string>
// Node 클래스를 사용하기 위한 rclcpp 헤더 파일
#include "rclcpp/rclcpp.hpp"
// 메시지 타입인 String 선언
#include "std_msgs/msg/string.hpp"
// 추후 500ms, 1s 와 같이 시간을 가시성 있게 문자로 표현하기 위한 namespace
using namespace std::chrono_literals;
// rclcpp의 Node 클래스를 상속하여 사용
class HelloworldPublisher : public rclcpp::Node
{
public:
// Node 클래스의 생성자를 호출, 노드 이름을 helloworld_publisher로 지정, count_는 0으로 초기화
HelloworldPublisher()
: Node("helloworld_publisher"), count_(0)
{
// QoS 설정을 위해 KeepLast 형태로 depth를 10으로 설정하여 퍼블리시할 데이터를 버퍼에 10개까지 저장
auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
// node클래스의 create_publisher함수를 이용하여 퍼블리셔 설정, 메시지 타입으로 String, 토픽 이름으로 helloworld, QoS
helloworld_publisher_ = this->create_publisher<std_msgs::msg::String>(
"helloworld", qos_profile);
// 콜백 함수를 수행, period=1초, 1초마다 지정한 콜백함수를 실행
timer_ = this->create_wall_timer(
1s, std::bind(&HelloworldPublisher::publish_helloworld_msg, this));
}
private:
// 콜백 함수
void publish_helloworld_msg()
{
// String 타입으로 msg 선언
auto msg = std_msgs::msg::String();
// 메시지 데이터를 입력
msg.data = "Hello World: " + std::to_string(count_++);
// logging, RCLCPP_XXX 계열의 함수는 print와 비슷
RCLCPP_INFO(this->get_logger(), "Published message: '%s'", msg.data.c_str());
// publishing
helloworld_publisher_->publish(msg);
}
// private 변수
rclcpp::TimerBase::SharedPtr timer_;
rclcpp::Publisher<std_msgs::msg::String>::SharedPtr helloworld_publisher_;
size_t count_;
};
int main(int argc, char * argv[])
{
// rclcpp 초기화
rclcpp::init(argc, argv);
// 클래스 생성
auto node = std::make_shared<HelloworldPublisher>();
// 콜백 함수 실행
rclcpp::spin(node);
// ctrl+c와 같은 인터럽트 시그널 예외 상황에서 노드 종료
rclcpp::shutdown();
return 0;
}
이때도, 멤버함수로 콜백함수를 선언했다. 만약 lambda로 구현하고자 한다면 publish_helloworld_msg 함수를 삭제하고 위의 HelloworldPublisher 클래스 생성자 구문의 timer_ = this->create_wall_timer() 함수에 람다 표현식을 추가하면 된다.
1
2
3
4
5
6
7
8
9
10
timer_ = this->create_wall_timer(
1s,
[this]() -> void
{
auto msg = std_msgs::msg::String();
msg.data = "Hello World2: " + std::to_string(count_++);
RCLCPP_INFO(this->get_logger(), "Published message: '%s'", msg.data.c_str());
helloworld_publisher_->publish(msg);
}
);
- 서브스크라이버 노드 생성
동일하게 ~/robot_ws/src/rclcpp_tutorial/src/
에 helloworld_subscriber.cpp 파일을 생성한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#include <functional>
#include <memory>
#include "rclcpp/rclcpp.hpp"
#include "std_msgs/msg/string.hpp"
// bind 함수의 대체자 역할을 위해 _1로 선언
using std::placeholders::_1;
// rclcpp의 Node클래스를 상속하여 사용
class HelloworldSubscriber : public rclcpp::Node
{
public:
// Node 클래스의 생성자를 호출하고 노드 이름을 helloworld_subscriber로 지정
HelloworldSubscriber()
: Node("Helloworld_subscriber")
{
// QoS depth 10으로 하여 버퍼에 10개 저장
auto qos_profile = rclcpp::QoS(rclcpp::KeepLast(10));
// 구독할 토픽의 메시지 타입과 토픽의 이름, QoS 설정, 수신받은 메시지를 처리할 콜백함수를 기입
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
{
// logging
RCLCPP_INFO(this->get_logger(), "Received message: '%s'", msg->data.c_str());
}
// private 변수로 사용되는 helloworld_subscriber_ 선언
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;
}
빌드
rclpy를 빌드할 때와 동일하다.
1
2
3
4
5
6
$ cd ~/robot_ws
$ colcon build --symlink-install --packages-select rclcpp_tutorial
Starting >>> rclcpp_tutorial
Finished <<< rclcpp_tutorial [11.8s]
Summary: 1 package finished [12.0s]
마지막으로 동일하게 bash파일을 실행시켜준다.
1
$ . ~/robot_ws/install/local_setup.bash
1
2
3
4
5
6
7
$ ros2 run rclcpp_tutorial helloworld_subscriber
[INFO]: Received message: 'Hello World: 0'
[INFO]: Received message: 'Hello World: 1'
[INFO]: Received message: 'Hello World: 2'
[INFO]: Received message: 'Hello World: 3'
[INFO]: Received message: 'Hello World: 4'
...
1
2
3
4
5
6
7
$ ros2 run rclcpp_tutorial helloworld_publisher
[INFO]: Published message: 'Hello World: 0'
[INFO]: Published message: 'Hello World: 1'
[INFO]: Published message: 'Hello World: 2'
[INFO]: Published message: 'Hello World: 3'
[INFO]: Published message: 'Hello World: 4'
...
ROS2 토픽, 서비스, 액션 인터페이스 살펴보기
추가 예정 |