본 포스팅은 ubuntu 20.04 , ros noetic 기준으로 설명 되어 있습니다!!
ROS Plugin이란?
- ros에서 프로그램 런타임에 특정 패키지를 load하여 사용할 수 있게 해주는 기능이다.
- 사전에 정의된 base interface를 상속받은 플러그인이면 어떤 것이든 사용 할 수 있게 해주며, 특정 기능에 대해 추상화를 잘 해준다.
ROS Plugin을 사용하면 좋은 때
- 특정 기능을 개발해야 하는데 기능의 인터페이스는 변하지 않고 내부 기능만 계속 변화할 때
- 특정 기능이 개발이 안돼있는 경우 mocking하여 임시로 개발할 때
- 상황에 따라 사용해야하는 기능이 달라질 때
Interface, BaseClass
plugin을 설계 할때, 먼저 만들고자 하는 기능의 interface를 정의 한다.
java, typescript 같은 언어는 interface라는 형식지 존재하지만 C++에서는 그런게 존재하지 않기 때문에 가장 기본되는 BaseClass를 생성하고 형식은 일반적으로 virtual function을 지정해 인터페이스를 정의한다.
Plugin Tutorial
모든 코드는 아래에 있습니다
https://github.com/ladianchad/plugin_study
1. Plugin package 생성
- package를 생성할때 pluginlib에 대한 dependency를 넣어준다
cd ~/{ros_workspace}/src
catkin_create_pkg plugin_study roscpp pluginlib
2. CMakeLists.txt , package.xml 확인
- CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)
project(plugin_study)
find_package(catkin REQUIRED COMPONENTS
pluginlib
roscpp
)
catkin_package(
INCLUDE_DIRS include
LIBRARIES plugin_study
CATKIN_DEPENDS pluginlib roscpp
DEPENDS system_lib
)
include_directories(
${catkin_INCLUDE_DIRS}
)
- package.xml
<?xml version="1.0"?>
<package format="2">
<name>plugin_study</name>
<version>0.0.0</version>
<description>The plugin_study package</description>
<maintainer email="ladianchad@todo.todo">ladianchad</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>pluginlib</build_depend>
<build_depend>roscpp</build_depend>
<build_export_depend>pluginlib</build_export_depend>
<build_export_depend>roscpp</build_export_depend>
<exec_depend>pluginlib</exec_depend>
<exec_depend>roscpp</exec_depend>
<export>
</export>
</package>
3. BaseClass 작성
- 기본적인 plugin을 소개하는 포스팅
- interface
- getName() : plugin이름을 반환하는 인터페이스
- action(): plugin이 특정 기능(여기선 그냥 ROS_INFO하는 걸로) 인터페이스
- include/plugin_study/base_study_plugin.hpp
#ifndef BASE_STUDY_PLUGIN__
#define BASE_STUDY_PLUGIN__
#include <string>
namespace study_plugin
{
class BaseStudyPlugin
{
protected:
std::string name_;
public:
BaseStudyPlugin(std::string name = "") : name_(name) {}
virtual std::string getName()
{
return name_;
};
virtual void action() = 0;
};
} // namespace study_plugin
#endif
4. Plugin 작성
cd ~/{ros_workspace}/src/plugin_study
mkdir plugins
mkdir src/plugin_study
- ROS_INFO , ROS_WARN, ROS_ERROR를 하는 플러그인을 각각 작성
- include/plugin_study/info_plugin.hpp
#ifndef INFO_STUDY_PLUGIN__
#define INFO_STUDY_PLUGIN__
#include <plugin_study/base_study_plugin.hpp>
namespace study_plugin
{
class InfoPlugin : public BaseStudyPlugin
{
public:
InfoPlugin();
virtual void action() override;
};
} // namespace study_plugin
#endif
- plugins/info_plugin.cpp
#include <plugin_study/info_plugin.hpp>
#include <ros/ros.h>
#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(study_plugin::InfoPlugin, study_plugin::BaseStudyPlugin);
namespace study_plugin
{
InfoPlugin::InfoPlugin() : BaseStudyPlugin("info plugin"){};
void InfoPlugin::action()
{
ROS_INFO("action : logging info plugin");
}
} // namespace study_plugin
- include/plugin_study/warn_plugin.hpp
#ifndef WARN_STUDY_PLUGIN__
#define WARN_STUDY_PLUGIN__
#include <plugin_study/base_study_plugin.hpp>
namespace study_plugin
{
class WarnPlugin : public BaseStudyPlugin
{
public:
WarnPlugin();
virtual void action() override;
};
} // namespace study_plugin
#endif
- plugins/warn_plugin.cpp
#include <plugin_study/warn_plugin.hpp>
#include <ros/ros.h>
#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(study_plugin::WarnPlugin, study_plugin::BaseStudyPlugin);
namespace study_plugin
{
WarnPlugin::WarnPlugin() : BaseStudyPlugin("warn plugin"){};
void WarnPlugin::action()
{
ROS_WARN("action : logging warn plugin");
}
} // namespace study_plugin
- include/plugin_study/error_plugin.hpp
#ifndef ERROR_STUDY_PLUGIN__
#define ERROR_STUDY_PLUGIN__
#include <plugin_study/base_study_plugin.hpp>
namespace study_plugin
{
class ErrorPlugin : public BaseStudyPlugin
{
public:
ErrorPlugin();
virtual void action() override;
};
} // namespace study_plugin
#endif
- plugins/error_plugin.cpp
#include <plugin_study/error_plugin.hpp>
#include <ros/ros.h>
#include <pluginlib/class_list_macros.h>
PLUGINLIB_EXPORT_CLASS(study_plugin::ErrorPlugin, study_plugin::BaseStudyPlugin);
namespace study_plugin
{
ErrorPlugin::ErrorPlugin() : BaseStudyPlugin("error plugin"){};
void ErrorPlugin::action()
{
ROS_WARN("action : logging error plugin");
}
} // namespace study_plugin
- 코드 설명
- 각각의 plugin들은 BaseClass인 BaseStudyPlugin을 상속받아 virtual fuction인 action을 구현하고 있다.
- pluginlib/class_list_macros.h에는 plugin을 export하게 해주는 매크로 함수인 PLUGINLIB_EXPORT_CLASS가 있다.
- PLUGINLIB_EXPORT_CLASS( target class , base class) 는 target class가 base class에 대한 plugin이라는 걸 export해준다.
- 모든 plugin은 인자를 받지 않는(default 값으로 해도 됨) 생성자를 필요로 한다. 이를 지키지 않으면 후에 plugin loader가 작동하지 않는다.
5. CMakeLists.txt , package.xml 수정 및 plugins.xml 작성
- CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)
project(plugin_study)
find_package(catkin REQUIRED COMPONENTS
pluginlib
roscpp
)
catkin_package(
INCLUDE_DIRS include
LIBRARIES plugin_study
CATKIN_DEPENDS pluginlib roscpp
DEPENDS system_lib
)
include_directories(
include ##추가
${catkin_INCLUDE_DIRS}
)
add_library(my_plugins
plugins/info_plugin.cpp
plugins/warn_plugin.cpp
plugins/error_plugin.cpp
)
add_dependencies(my_plugins ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(my_plugins ${catkin_LIBRARIES})
install(TARGETS
my_plugins
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
)
install(FILES plugins.xml
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
- 설명
- add_library : plugin library 생성
- add_dependencies: library dependency 정의
- target_link_libraries : library link
- install: library install
- package.xml
<?xml version="1.0"?>
<package format="2">
<name>plugin_study</name>
<version>0.0.0</version>
<description>The plugin_study package</description>
<maintainer email="ladianchad@todo.todo">ladianchad</maintainer>
<license>TODO</license>
<buildtool_depend>catkin</buildtool_depend>
<build_depend>pluginlib</build_depend>
<build_depend>roscpp</build_depend>
<build_export_depend>pluginlib</build_export_depend>
<build_export_depend>roscpp</build_export_depend>
<exec_depend>pluginlib</exec_depend>
<exec_depend>roscpp</exec_depend>
<export>
<plugin_study plugin="${prefix}/plugins.xml"/>
</export>
</package>
- 설명
- plugin_study / plugin : plugin export를 위한 xml file 정의
- plugins.xml
<class_libraries>
<library path="libmy_plugins">
<class type="study_plugin::InfoPlugin" base_class_type="study_plugin::BasePlugin">
<description>action is info warn</description>
</class>
<class type="study_plugin::WarnPlugin" base_class_type="study_plugin::BasePlugin">
<description>action is loggin warn</description>
</class>
<class type="study_plugin::ErrorPlugin" base_class_type="study_plugin::BasePlugin">
<description>action is info error</description>
</class>
</library>
</class_libraries>
- 설명
- library / path : CMakeLists.txt에서 “lib” + “add_library로 했던 library이름”으로 path가 자동 생성된다.
- class / type : plugin의 type을 적어준다. namespace안에 있는 class면 namespace부터 적어준다.
- class / base_class_type : plugin의 base class를 명시한다.
- class / description : plugin의 간단한 설명을 적어 놓는다.
6. plugin load & test
- 만든 plugin을 test하기 위해 node 한개를 추가하기 위해 다음과 같이 한다.
- src/main.cpp
#include <ros/ros.h>
#include <pluginlib/class_loader.h>
#include <plugin_study/base_study_plugin.hpp>
int main(int argc, char **argv)
{
ros::init(argc, argv, "plugin_study");
ros::NodeHandle nh;
// plugin loader 생성
pluginlib::ClassLoader<study_plugin::BaseStudyPlugin> plugin_loader("plugin_study", "study_plugin::BasePlugin");
// plugin load
boost::shared_ptr<study_plugin::BaseStudyPlugin> plugin = plugin_loader.createInstance("study_plugin::InfoPlugin");
ROS_INFO("plugin name : %s", plugin->getName().c_str());
while (ros::ok())
{
plugin->action();
ros::Duration(1).sleep();
}
return 0;
}
- 설명
- pluginlib::ClassLoader<{BaseClasee}> : plugin을 동적으로 load해주는 class이다 (기본적으로 factory pattern이다). 생성자에는 plugin이 들어있는 package와 plugin의 base class type을 적어주면 된다.
- plugin_loader.createInstance({target plugin type}): load하고자 하는 plugin의 type을 적어주면 해당하는 plugin을 load해준다. 여기에 해당하는 type을 launch prameter로 받게 되면 소스코드는 다시 빌드하지 않고 plugin만 새로 빌드 해서 넣으면 전혀 다른 기능이 작동하게 만들 수 있다.
- CMakeLists.txt
cmake_minimum_required(VERSION 3.0.2)
project(plugin_study)
find_package(catkin REQUIRED COMPONENTS
pluginlib
roscpp
)
catkin_package(
INCLUDE_DIRS include
LIBRARIES plugin_study
CATKIN_DEPENDS pluginlib roscpp
DEPENDS system_lib
)
include_directories(
include ##추가
${catkin_INCLUDE_DIRS}
)
add_library(my_plugins
plugins/info_plugin.cpp
plugins/warn_plugin.cpp
plugins/error_plugin.cpp
)
add_dependencies(my_plugins ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})
target_link_libraries(my_plugins ${catkin_LIBRARIES})
##추가
add_executable(plugin_test src/main.cpp)
target_link_libraries(plugin_test ${catkin_LIBRARIES})
##추가
install(TARGETS
my_plugins
ARCHIVE DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
LIBRARY DESTINATION ${CATKIN_PACKAGE_LIB_DESTINATION}
RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION}
)
install(FILES plugins.xml
DESTINATION ${CATKIN_PACKAGE_SHARE_DESTINATION}
)
이제 빌드하고 plugin_study plugin_test를 실행하면 정상작동하는 걸 볼 수 있다.
cd ~/{ros_workspace}
catkin_make
source devel/setup.bash
728x90
'ROS' 카테고리의 다른 글
[ROS] Move Base (3) Move Base Code 분석 (0) | 2022.02.21 |
---|---|
[ROS] Move Base (2) Velocity & Acceleration (0) | 2022.02.18 |
[ROS] Move Base (1) Navigation Stack (2) | 2022.02.18 |
[ROS] XmlRpc 사용법 (0) | 2022.02.14 |
[ Server / Client ] 서버 클라이언트 작성법 + Custom service file 작성법 (1) | 2021.01.30 |