Gazebo plugins give your URDF models greater functionality and can tie in ROS messages and service calls for sensor output and motor input.
Gazebo API는 물체를 움직이거나 센서 데이터를 받아오거나 등등의 다양한 기능을 제공하는데 이때 plugin은 이 API에 접근이 가능하도록 해준다.
Plugin은 C++ library로
cd ~/sim_ws/src
catkin_create_pkg writing_plugins gazebo gazebo_ros gazebo_plugins roscpp
code ~/sim_ws/src/writing_plugins/src/my_gazebo_plugin.cc
.cc 파일이 처음인 분도 있을 것이다.
오래된 code base나 embedded system programming에서 사용되는 확장자로
c with classes의 약어이고 대부분 cpp compiler에서 작동한다.
gazebo는 plugin을 만들 때 .cc 파일을 활용한다.
<my_gazebo_plugin.cc>
#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>
#include <iostream>
namespace gazebo{
class MyGazeboPlugin : public WorldPlugin {
public:
MyGazeboPlugin() : WorldPlugin() {
std::cout << "Plugin constructor method!" << std::endl;
}
public:
void Load(physics::WorldPtr _world, sdf::ElementPtr _sdf) {
std::cout << "Everything is awesome!" << std::endl;
}
};
// register plugin - why no semicolon..?
GZ_REGISTER_WORLD_PLUGIN(MyGazeboPlugin)
}
GZ_REGISTER_WORLD_PLUGIN : gazebo에 plugin을 등록하는데 사용되는 매크로 호출이다.
.
<CMakeLists.txt> 도 수정해줘야한다.
cmake_minimum_required(VERSION 3.0.2)
project(writing_plugins)
find_package(gazebo REQUIRED)
find_package(catkin REQUIRED COMPONENTS
gazebo_plugins
gazebo_ros
roscpp
)
catkin_package()
include_directories(
# include
${catkin_INCLUDE_DIRS}
)
include_directories(${GAZEBO_INCLUDE_DIRS})
link_directories(${GAZEBO_LIBRARY_DIRS})
list(APPEND CMAKE_CXX_FLAGS "${GAZEBO_CXX_FLAGS}")
add_library(my_gazebo_plugin SHARED src/my_gazebo_plugin.cc)
target_link_libraries(my_gazebo_plugin ${GAZEBO_LIBRARIES})
.
이제 build를 해준다. 빌드가 되고나면 이제 plugin을 사용할 준비가 되었다는 뜻이다.
나는 catkin_make를 이용해서 계속 package를 빌드해왔다.
정확히는 alias를 정의해서 'cd ~/sim_ws && catkin_make' 이렇게 한번에 해주고 있다.
문득 catkin build랑 catkin_make는 뭐가 다른지 궁금해서 정리해뒀으니 궁금한 사람은 읽어보자 👍
*한번 catkin_make로 빌드하고 나면 catkin build로 빌드가 안됨
cd ~/sim_ws
catkin build
.
[궁금증] catkin_make vs catkin build ?
- catkin build를 사용하면 전체적인 build configuration이 훨씬 견고해지고
isolated package는 병렬로 빌드되므로 훨씬 빠르다.- catkin build는 아무 directory에서나 가능하지만 catkin_make는 top level directory에서만 가능하다.
- single package만 build할수도 있다. - catkin build (package_name)
- catkin build는 catkin clean, install, list ... 등등의 다양한 subcommands도 제공한다.
.
.
simulation을 실행할 때마다 terminal에서 선언해주면 된다.
.
참고로 띄어쓰기를 하면 안된다 ㅎㅎ
export GAZEBO_PLUGIN_PATH=${GAZEBO_PLUGIN_PATH}:~/sim_ws/devel/lib
.
mkdir -p ~/sim_ws/src/writing_plugins/launch
code ~/sim_ws/src/writing_plugins/launch/gazebo.launch
mkdir -p ~/sim_ws/src/writing_plugins/worlds
code ~/sim_ws/src/writing_plugins/launch/gazebo.world
[궁금증] mkdir -p는?
mkdir 뒤에 옵션으로 붙는 -p는 상위폴더도 함께 생성하라는 의미이다.
그 외에도 -v는 생성하고 생성된 경로를 메세지 출력 등의 옵션이 있습니다.
.
.
<gazebo.launch>
<launch>
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="world_name" value="$(find writing_plugins)/worlds/gazebo.world"/>
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="gui" value="true"/>
<arg name="headless" value="false"/>
<arg name="debug" value="false"/>
</include>
</launch>
<gazebo.world>
<?xml version="1.0"?>
<sdf version="1.4">
<world name="default">
<plugin name="my_gazebo_plugin" filename="libmy_gazebo_plugin.so"/>
</world>
</sdf>
.world 파일이 visual studio code에서 xml로 인식이 잘 안된다면
아래 링크를 참고해 vsc상에서 설정해주자
vsc 파일 확장자 설정하기
roslaunch writing_plugins gazebo.launch
실행시키고 나면 위에서 <my_gazebo_plugin.cc>가 실행되어 아래내용이 print된다.
Plugin constructor method!
Everything is awesome!
.
.
위에서 만든 my_gazebo_plugin.cc에서 Load 함수 부분만 변경해주면 되지만
나는 더 정확히 이해하기 위해
다른 이름 my_gazebo_plugin_world.cc 를 만들어주었다.
<my_gazebo_plugin_world.cc>
#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>
#include <iostream>
namespace gazebo{
class MyGazeboPlugin : public WorldPlugin {
public:
MyGazeboPlugin() : WorldPlugin() {
std::cout << "Plugin constructor method!" << std::endl;
}
public:
void Load(physics::WorldPtr _world, sdf::ElementPtr _sdf){
//set a node
transport::NodePtr node(new transport::Node());
node->Init(_world->Name());
//set publisher
transport::PublisherPtr publisher=node->Advertise<msgs::Factory>("~/factory");
//create msg obj
msgs::Factory msg;
//model to use
msg.set_sdf_filename("model://jersey_barrier");
//send the message
publisher->Publish(msg);
}
};
GZ_REGISTER_WORLD_PLUGIN(MyGazeboPlugin)
}
.
.
.
이미 위에서 CMakeList.txt를 수정했기 때문에 1줄만 추가해주면 된다.
add_library(my_gazebo_plugin_world SHARED src/my_gazebo_plugin_world.cc)
그 다음 build를 해주면 .so 파일이 생성된 걸 확인할 수 있다.
.
code ~/sim_ws/src/writing_plugins/worlds/gazebo_world.world
<gazebo_world.world>
<?xml version="1.0"?>
<sdf version="1.4">
<world name="default">
<plugin name="my_gazebo_plugin_world" filename="libmy_gazebo_plugin_world.so"/>
</world>
</sdf>
code ~/sim_ws/src/writing_plugins/launch/gazebo_world.launch
<gazebo_world.launch>
<launch>
<include file="$(find gazebo_ros)/launch/empty_world.launch">
<arg name="world_name" value="$(find writing_plugins)/worlds/gazebo_world.world"/>
<arg name="paused" value="false"/>
<arg name="use_sim_time" value="true"/>
<arg name="gui" value="true"/>
<arg name="headless" value="false"/>
<arg name="debug" value="false"/>
</include>
</launch>
.
.
launch 파일을 실행하면
gazebo에 jersey_barrier가 생긴 것을 볼 수 있다.
힌트
msgs::Set(msg.mutable_pose(),
ignition::math::Pose3d(
ignition::math::Vector3d(5,5,0),
ignition::math::Quaterniond(0,0,0))
);
.
<world_plugin.cc>
#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>
#include <iostream>
namespace gazebo{
class MyGazeboPlugin : public WorldPlugin {
public:
MyGazeboPlugin() : WorldPlugin() {
std::cout << "Plugin constructor method!" << std::endl;
}
public:
void Load(physics::WorldPtr _world, sdf::ElementPtr _sdf){
//set a node
transport::NodePtr node(new transport::Node());
node->Init(_world->Name());
//set publisher
transport::PublisherPtr publisher=node->Advertise<msgs::Factory>("~/factory");
//create msg obj
msgs::Factory msg;
//model to use
msg.set_sdf_filename("model://jersey_barrier");
msgs::Set(msg.mutable_pose(),
ignition::math::Pose3d(
ignition::math::Vector3d(5,5,0),
ignition::math::Quaterniond(0,0,0))
);
//send the message
publisher->Publish(msg);
}
};
GZ_REGISTER_WORLD_PLUGIN(MyGazeboPlugin)
}
.
.
code ~/sim_ws/src/writing_plugins/worlds/model.world
code ~/sim_ws/src/writing_plugins/src/my_model_plugin.cc
code ~/sim_ws/src/writing_plugins/launch/model.launch
<my_model_plugin.cc>
#include <functional>
#include <gazebo/common/common.hh>
#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>
#include <ignition/math/Vector3.hh>
namespace gazebo {
class MyModelPlugin : public ModelPlugin {
public:
void Load(physics::ModelPtr _parent, sdf::ElementPtr) {
// Store the pointer to the model
this->model = _parent;
// Listen to the update event. This event is broadcast every simulation iteration.
this->updateConnection = event::Events::ConnectWorldUpdateBegin(
std::bind(&MyModelPlugin::OnUpdate, this));
}
// Called by the world update start event
public:
void OnUpdate() {
// Apply a small linear velocity to the model.
if (this->counter < 10000) {
this->model->SetLinearVel(ignition::math::Vector3d(0, 0, 0.4));
}
this->counter++;
}
// Pointer to the model
private:
physics::ModelPtr model;
private:
int counter;
// Pointer to the update event connection
private:
event::ConnectionPtr updateConnection;
};
// Register this plugin with the simulator
GZ_REGISTER_MODEL_PLUGIN(MyModelPlugin)
} // namespace gazebo
OnUpdate : 반복 주기에 의한 callback 함수
모델이 위쪽으로 움직이게 되는 이유는 setLinearVel에서 Z 축 방향으로 0.4만큼의 velocity를 계속 인가하고 있기 때문, 관련링크 : setLinearVel
.
.
<CMakeList.txt> 파일에서 아래 내용을 수정해준 후 빌드를 해주면 된다.
add_library(my_model_plugin SHARED src/my_model_plugin.cc)
target_link_libraries(my_model_plugin ${GAZEBO_LIBRARIES})
model plugin의 경우, world 파일에서 불러와줘야 하므로
<model.world> 파일에서 model 태그 안에 내용을 수정해줘야 한다.
<?xml version="1.0"?>
<sdf version="1.4">
<world name="default">
<!-- Ground Plane -->
<include>
<uri>model://ground_plane</uri>
</include>
<include>
<uri>model://sun</uri>
</include>
<model name="box">
<pose>0 0 0.5 0 0 0</pose>
<link name="link">
<collision name="collision">
<geometry>
<box size="1.0 1.0 1.0"/>
</geometry>
</collision>
<visual name="visual">
<geometry>
<box size="1.0 1.0 1.0"/>
</geometry>
</visual>
</link>
<!-- import plugin -->
<plugin name="my_model_plugin" filename="libmy_model_plugin.so"/>
</model>
</world>
</sdf>
launch 파일도 생성해준다 ( 앞에서 많이 했으니 생략하겠다. )
정육면체가 위로 올라가는 것을 확인할 수 있다.
.
.
<hint code - load() - .cc 코드에 넣기>
this -> iterations = 10 * 1000;
if (_sdf->HasElement("iterations")){
this->iterations=_sdf->Get<int>("iterations");
}
<model.world>
<plugin name="a_model_plugin" filename="liba_model_plugin.so">
<linear_vel>0.2</linear_vel>
<iterations>15000</iterations>
</plugin>
.
.
포인터가 많이나와서 .. 포인터를 열심히 공부해야겠다 생각했다..
<a_model_plugin.cc>
#include <functional>
#include <gazebo/common/common.hh>
#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>
#include <ignition/math/Vector3.hh>
#include <ros/ros.h>
namespace gazebo {
class AModelPlugin : public ModelPlugin {
public:
void Load(physics::ModelPtr _parent, sdf::ElementPtr _sdf) {
//store the pointer to the model
this->model = _parent;
this -> iterations = 10 * 1000;
if (_sdf->HasElement("iterations")){
this->iterations=_sdf->Get<int>("iterations");
}
this -> linear_vel = 0.0;
if (_sdf->HasElement("linear_vel")){
this->linear_vel=_sdf->Get<double>("linear_vel");
}
this->updateConnection = event::Events::ConnectWorldUpdateBegin(
std::bind(&AModelPlugin::OnUpdate, this)
);
}
// Called by the world update start event
public:
void OnUpdate() {
// apply a small linear velocity to the model
if(this->counter < this->iterations)
{
this->model->SetLinearVel(ignition::math::Vector3d(this->linear_vel,0,0));
}
else if(this->counter < 2 * this->iterations)
{
this->model->SetLinearVel(ignition::math::Vector3d(0,this->linear_vel,0));
}
else if(this->counter < 3 * this->iterations)
{
this->model->SetLinearVel(ignition::math::Vector3d(-this->linear_vel,0,0));
}
else if(this->counter < 4 * this->iterations)
{
this->model->SetLinearVel(ignition::math::Vector3d(0,-this->linear_vel,0));
}
else
{
this->counter = 0;
}
this->counter++;
}
private:
physics::ModelPtr model;
private:
int counter;
double linear_vel;
int iterations;
// Pointer to the update event connection
private:
event::ConnectionPtr updateConnection;
};
// register this plugin with the simulation
GZ_REGISTER_MODEL_PLUGIN(AModelPlugin)
}
아래 내용을 추가해주고~
add_library(a_model_plugin SHARED src/a_model_plugin.cc)
target_link_libraries(a_model_plugin ${GAZEBO_LIBRARIES})
<?xml version="1.0"?>
<sdf version="1.4">
<world name="default">
<!-- Ground Plane -->
<include>
<uri>model://ground_plane</uri>
</include>
<include>
<uri>model://sun</uri>
</include>
<model name="a_barrier">
<pose>1 1 0 0 0 0</pose>
<static>false</static>
<include>
<uri>model://suv</uri>
</include>
<!-- import plugin -->
<plugin name="a_model_plugin" filename="liba_model_plugin.so">
<linear_vel>5.0</linear_vel>
<iterations>5000</iterations>
</plugin>
</model>
</world>
</sdf>
launch 파일은 어렵지 않으니 생략하겠다...
사각형패턴으로 움직이는 것을 확인할 수 있다.
정말 분량이 많아서 오래걸리고 힘들었지만.....
그래도 ... 반복해서 하다보니 plugin을 만드는 flow를 이해하는데 도움이 되었다!!
c++ 공부에 더 매진해야겠다.
난 이제 지쳤어요 땡벌땡벌