【Gazebo入门教程】第六讲 控制器插件的编写与配置(下)

2023-05-16

【Gazebo入门教程】第六讲 控制器插件的编写与配置(下)

在这里插入图片描述

\qquad

文章目录

  • 【Gazebo入门教程】第六讲 控制器插件的编写与配置(下)
    • 一、系统插件
    • 二、Velodyne传感器插件
      • 1. 基本插件文件创建
      • 2. 插件连接与测试
      • 3. 插件配置
    • 三、创造API
      • 1. 基本概念
      • 2. 方法具体实现
      • 3. 测试API
  • 总结

前言在上一篇博客中,我们首先了解了控制器插件的具体使用方法和配置流程,采用多个实例了解了模型插件和世界插件等的具体使用方法,本节博客将继续深入体会插件的功能效用,以两个实例重点介绍系统插件和传感器插件的配置方法,其中传感器插件的配置是重点,与之前的教程一致,可统一学习,最后通过创造API实现动态调整。


一、系统插件

  • 目标:给gzclient设计系统插件,将图像存储到/tmp/gazebo_frames目录下方

  • 创建并编写插件文件:

  1. 创建插件文件:
cd ~/gazebo_plugin_tutorial
gedit system_gui.cc 
  1. 编写插件文件:
#include <functional>
#include <gazebo/gui/GuiIface.hh>
#include <gazebo/rendering/rendering.hh>
#include <gazebo/gazebo.hh>

namespace gazebo
{
 class SystemGUI : public SystemPlugin
 {
   // Destructor
   public: virtual ~SystemGUI()
   {
     this->connections.clear();
     if (this->userCam)
       this->userCam->EnableSaveFrame(false);
     this->userCam.reset();
   }

   // 在启动时,Gazebo加载之前,Load和Init会在插件被构造后调用,且二者必须不能阻塞(must not block) 
   public: void Load(int /*_argc*/, char ** /*_argv*/)
   {
     this->connections.push_back(
         event::Events::ConnectPreRender(
           std::bind(&SystemGUI::Update, this)));
   }

   // 只在`Load`后调用一次
   private: void Init()
   {
   }

   // 每次PreRender事件都会调用,看`Load`函数
   private: void Update()
   {
     if (!this->userCam)
     {
       // 得到一个激活用户相机的指针
       this->userCam = gui::get_active_camera();

       // 开启帧的保存
       this->userCam->EnableSaveFrame(true);

       // 指定要保存帧的路径
       this->userCam->SetSaveFramePathname("/tmp/gazebo_frames");
     }

     // 得到scene的指针
     rendering::ScenePtr scene = rendering::get_scene();

     // 等待,直到scene被初始化了
     if (!scene || !scene->Initialized())
       return;

     // 通过名字寻找一个特定的图像
     if (scene->GetVisual("ground_plane"))
       std::cout << "Has ground plane visual\n";
   }

   // 申明用户相机的指针
   private: rendering::UserCameraPtr userCam;

   // 申明存储所有事件连接的vector
   private: std::vector<event::ConnectionPtr> connections;
 };

 // 和模拟器上注册插件
 GZ_REGISTER_SYSTEM_PLUGIN(SystemGUI)
}
  • 增加编译规则并进行编译:
  1. 修改编译规则(底部添加):
add_library(system_gui SHARED system_gui.cc)
target_link_libraries(system_gui ${GAZEBO_LIBRARIES})
  1. 编译插件:
cd ~/gazebo_plugin_tutorial/build
cmake ..
make
  • 启动并运行服务器、客户端和插件:
gzserver & 
gzclient -g libsystem_gui.so
  • 使用效果:
  1. tmp/gazebo_frames目录下,应该会出现一些照片

在这里插入图片描述

  1. 在同一个终端输入如下代码终止后台运行的程序fg
  2. 按Ctrl+C终止进程

二、Velodyne传感器插件

在这里插入图片描述


1. 基本插件文件创建

  • 创建工作区:
mkdir ~/velodyne_plugin
cd ~/velodyne_plugin
  • 创建插件源文件:
gedit velodyne_plugin.cc
  • 编写插件源文件:
#ifndef _VELODYNE_PLUGIN_HH_
#define _VELODYNE_PLUGIN_HH_

#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>

namespace gazebo
{
  /// \brief A plugin to control a Velodyne sensor.
  class VelodynePlugin : public ModelPlugin
  {
    /// \brief Constructor
    public: VelodynePlugin() {}

    /// \brief The load function is called by Gazebo when the plugin is
    /// inserted into simulation
    /// \param[in] _model A pointer to the model that this plugin is
    /// attached to.
    /// \param[in] _sdf A pointer to the plugin's SDF element.
    public: virtual void Load(physics::ModelPtr _model, sdf::ElementPtr _sdf)
    {
      // Just output a message for now
      std::cerr << "\nThe velodyne plugin is attach to model[" <<
        _model->GetName() << "]\n";
    }
  };

  // Tell Gazebo about this plugin, so that Gazebo can call Load on this plugin.
  GZ_REGISTER_MODEL_PLUGIN(VelodynePlugin)
}
#endif
  • 创建编译规则文件构建脚本:
gedit CMakeLists.txt
cmake_minimum_required(VERSION 2.8 FATAL_ERROR)

# Find Gazebo
find_package(gazebo REQUIRED)
include_directories(${GAZEBO_INCLUDE_DIRS})
link_directories(${GAZEBO_LIBRARY_DIRS})
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${GAZEBO_CXX_FLAGS}")

# Build our plugin
add_library(velodyne_plugin SHARED velodyne_plugin.cc)
target_link_libraries(velodyne_plugin ${GAZEBO_LIBRARIES})

2. 插件连接与测试

  • 将插件附加到SDF文件中,连接到传感器,并通过include功能进行测试:
  1. 创建世界文件:
gedit velodyne.world 
  1. 编写世界文件:
<?xml version="1.0" ?>
<sdf version="1.5">
 <world name="default">
   <!-- A global light source -->
   <include>
     <uri>model://sun</uri>
   </include>
   <!-- A ground plane -->
   <include>
     <uri>model://ground_plane</uri>
   </include>

   <!-- A testing model that includes the Velodyne sensor model -->
   <model name="my_velodyne">
     <include>
       <uri>model://velodyne_hdl32</uri>
     </include>

     <!-- Attach the plugin to this model -->
     <plugin name="velodyne_control" >filename="libvelodyne_plugin.so"/>
   </model>

 </world>
</sdf>
  1. 构造目录并编译文件:
cd ~/gazebo_plugin_tutorial/build/
cmake ..
make
  1. 添加库路径并从build目录中运行gazebo:
cd ~/velodyne_plugin/build
cexport LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:~/velodyne_plugin/build
cgazebo ../velodyne.world
  1. 效果展示:
The velodyne plugin is attached to model[my_velodyne]

3. 插件配置

  • 插件配置:使用PID控制传感器关节速度
  1. 修改插件源文件
gedit ~/velodyne_plugin/velodyne_plugin.cc
  1. 修改Load函数,代码如下:
public: virtual void Load(physics::ModelPtr _model, sdf::ElementPtr _sdf)
{
 // Safety check
 if (_model->GetJointCount() == 0)
 {
   std::cerr << "Invalid joint count, Velodyne plugin not loaded\n";
   return;
 }

 // Store the model pointer for convenience.
 this->model = _model;

 // Get the first joint. We are making an assumption about the model
 // having one joint that is the rotational joint.
 this->joint = _model->GetJoints()[0];

 // Setup a P-controller, with a gain of 0.1.
 this->pid = common::PID(0.1, 0, 0);

 // Apply the P-controller to the joint.
 this->model->GetJointController()->SetVelocityPID(
     this->joint->GetScopedName(), this->pid);

 // Set the joint's target velocity. This target velocity is just
 // for demonstration purposes.
 this->model->GetJointController()->SetVelocityTarget(
     this->joint->GetScopedName(), 10.0);
}
  1. 将如下私有成员添加到对应类中:
/// \brief Pointer to the model.
private: physics::ModelPtr model;

/// \brief Pointer to the joint.
private: physics::JointPtr joint;

/// \brief A PID controller for the joint.
private: common::PID pid;
  1. 在世界文件中配置插件,读取自定义SDF参数:
gedit ~/gazebo_plugin_tutorial/velodyne.world
  1. 修改标签来包含一个元素:
<plugin name="velodyne_control" filename="libvelodyne_plugin.so">
 <velocity>25</velocity>
</plugin>
  1. 重新修改插件文件中的Load函数的底部,使用 sdf::ElementPtr参数来读取:
// 默认速度为0
double velocity = 0;

// 检查是否<velocity>元素存在,然后读取数值
if (_sdf->HasElement("velocity"))
 velocity = _sdf->Get<double>("velocity");

// 设置关节的目标速度
this->model->GetJointController()->>SetVelocityTarget(this->joint->GetScopedName(), >velocity); 
  1. 效果: 重新编译并运行gazebo,修改,传感器应进行旋转
cd ~/velodyne_plugin/build
cmake ..
make
gazebo --verbose ../velodyne.world

三、创造API

在这里插入图片描述


1. 基本概念

  • 目的:动态调整目标速度,无需手动修改SDF文件
  • 分类:此处可以实现两种API类型:消息传递,和函数【此处我们可以同时实现
  1. 消息传递:
    依赖于Gazebo的传输机制,它将涉及创建一个命名的主题,发布者可以在该主题上发送double值。这样插件将接受到这些消息,并正确地设置速度值。对于进程间通信,消息传递是很方便的。
  2. 函数法:
    新建一个公共函数来调整速度值。一个新的插件将继承我们当前的插件。子级插件将被实例化(而不是我们当前的插件),通过调用函数,我们可以控制速度。当Gazebo与ROS交互时,这一方法最常用。

2. 方法具体实现

  • 2.1 打开插件文件:
gedit ~/gazebo_plugin_tutorial/velodyne_plugin.cc
  • 2.2 新建一个可以设置目标速度的公共函数:
/// \brief Set the velocity of the Velodyne
/// \param[in] _vel New target velocity
public: void SetVelocity(const double &_vel)
{
  // Set the joint's target velocity.
  this->model->GetJointController()->SetVelocityTarget(this->joint->GetScopedName(), _vel);
}
  • 2.3 在插件中添加一个节点和订阅者,设置消息结构:
/// \brief A node used for transport
private: transport::NodePtr node;

/// \brief A subscriber to a named topic.
private: transport::SubscriberPtr sub;
  • 2.4 在Load函数底部实例化节点和订阅者,node和 subscriber:
// 创造节点
this->node = transport::NodePtr(new transport::Node());
#if GAZEBO_MAJOR_VERSION < 8
this->node->Init(this->model->GetWorld()->GetName());
#else
this->node->Init(this->model->GetWorld()->Name());
#endif

// 创造一个主题名
std::string topicName = "~/" + this->model->GetName() + "/vel_cmd";

// 订阅这个主题,并且注册一个回调
this->sub = this->node->Subscribe(topicName, &VelodynePlugin::OnMsg, this); 
  • 2.5 新建回调函数来处理接受到的信息:
/// \brief Handle incoming message
/// \param[in] _msg Repurpose a vector3 message. This function will
/// only use the x component.
private: void OnMsg(ConstVector3dPtr &_msg)
{
  this->SetVelocity(_msg->x());
}
  • 2.6 给消息传递机制添加两个额外头文件:
#include <gazebo/transport/transport.hh>
#include <gazebo/msgs/msgs.hh>
  • 2.7 完整代码如下:
#ifndef _VELODYNE_PLUGIN_HH_
#define _VELODYNE_PLUGIN_HH_

#include <gazebo/gazebo.hh>
#include <gazebo/physics/physics.hh>
#include <gazebo/transport/transport.hh>
#include <gazebo/msgs/msgs.hh>

namespace gazebo
{
  /// \brief A plugin to control a Velodyne sensor.
  class VelodynePlugin : public ModelPlugin
  {
    /// \brief Constructor
    public: VelodynePlugin() {}

    /// \brief The load function is called by Gazebo when the plugin is
    /// inserted into simulation
    /// \param[in] _model A pointer to the model that this plugin is
    /// attached to.
    /// \param[in] _sdf A pointer to the plugin's SDF element.
    public: virtual void Load(physics::ModelPtr _model, sdf::ElementPtr _sdf)
    {
      // Safety check
      if (_model->GetJointCount() == 0)
      {
        std::cerr << "Invalid joint count, Velodyne plugin not loaded\n";
        return;
      }

      // Store the model pointer for convenience.
      this->model = _model;

      // Get the first joint. We are making an assumption about the model
      // having one joint that is the rotational joint.
      this->joint = _model->GetJoints()[0];

      // Setup a P-controller, with a gain of 0.1.
      this->pid = common::PID(0.1, 0, 0);

      // Apply the P-controller to the joint.
      this->model->GetJointController()->SetVelocityPID(
          this->joint->GetScopedName(), this->pid);

      // Default to zero velocity
      double velocity = 0;

      // Check that the velocity element exists, then read the value
      if (_sdf->HasElement("velocity"))
        velocity = _sdf->Get<double>("velocity");

      this->SetVelocity(velocity);

      // Create the node
      this->node = transport::NodePtr(new transport::Node());
      #if GAZEBO_MAJOR_VERSION < 8
      this->node->Init(this->model->GetWorld()->GetName());
      #else
      this->node->Init(this->model->GetWorld()->Name());
      #endif

      // Create a topic name
      std::string topicName = "~/" + this->model->GetName() + "/vel_cmd";

      // Subscribe to the topic, and register a callback
      this->sub = this->node->Subscribe(topicName,
         &VelodynePlugin::OnMsg, this);
    }

    /// \brief Set the velocity of the Velodyne
    /// \param[in] _vel New target velocity
    public: void SetVelocity(const double &_vel)
    {
      // Set the joint's target velocity.
      this->model->GetJointController()->SetVelocityTarget(
          this->joint->GetScopedName(), _vel);
    }

    /// \brief Handle incoming message
    /// \param[in] _msg Repurpose a vector3 message. This function will
    /// only use the x component.
    private: void OnMsg(ConstVector3dPtr &_msg)
    {
      this->SetVelocity(_msg->x());
    }

    /// \brief A node used for transport
    private: transport::NodePtr node;

    /// \brief A subscriber to a named topic.
    private: transport::SubscriberPtr sub;

    /// \brief Pointer to the model.
    private: physics::ModelPtr model;

    /// \brief Pointer to the joint.
    private: physics::JointPtr joint;

    /// \brief A PID controller for the joint.
    private: common::PID pid;
  };

  // Tell Gazebo about this plugin, so that Gazebo can call Load on this plugin.
  GZ_REGISTER_MODEL_PLUGIN(VelodynePlugin)
}
#endif

3. 测试API

  • 在gazebo_plugin_tutorial目录下新建源文件:
gedit ~/velodyne_plugin/vel.cc
  • 编写API文件:
#include <gazebo/gazebo_config.h>
#include <gazebo/transport/transport.hh>
#include <gazebo/msgs/msgs.hh>

// Gazebo's API has changed between major releases. These changes are
// accounted for with #if..#endif blocks in this file.
#if GAZEBO_MAJOR_VERSION < 6
#include <gazebo/gazebo.hh>
#else
#include <gazebo/gazebo_client.hh>
#endif

int main(int _argc, char **_argv)
{
  // 将Gazebo加载为客户端
#if GAZEBO_MAJOR_VERSION < 6
  gazebo::setupClient(_argc, _argv);
#else
  gazebo::client::setup(_argc, _argv);
#endif

  // 为了通信,创建我们的节点
  gazebo::transport::NodePtr node(new gazebo::transport::Node());
  node->Init();

  // 发布到velodyne传感器的主题
  gazebo::transport::PublisherPtr pub =
    node->Advertise<gazebo::msgs::Vector3d>("~/my_velodyne/vel_cmd");

  // 等待订阅者连接到发布者
  pub->WaitForConnection();

  // 创建一个vector3消息
  gazebo::msgs::Vector3d msg;

  // 设置x方向的速度
#if GAZEBO_MAJOR_VERSION < 6
  gazebo::msgs::Set(&msg, gazebo::math::Vector3(std::atof(_argv[1]), 0, 0));
#else
  gazebo::msgs::Set(&msg, ignition::math::Vector3d(std::atof(_argv[1]), 0, 0));
#endif

  // 发送消息
  pub->Publish(msg);

  // 确保所有都关闭了
#if GAZEBO_MAJOR_VERSION < 6
  gazebo::shutdown();
#else
  gazebo::client::shutdown();
#endif
}
  • 添加编译规则(底部):
# Build the stand-alone test program
add_executable(vel vel.cc)

if (${gazebo_VERSION_MAJOR} LESS 6)
  # These two
  include(FindBoost)
  find_package(Boost ${MIN_BOOST_VERSION} REQUIRED system filesystem regex)
  target_link_libraries(vel ${GAZEBO_LIBRARIES} ${Boost_LIBRARIES})
else()
  target_link_libraries(vel ${GAZEBO_LIBRARIES})
endif()
  • 编译程序:
cd ~/gazebo_plugin_tutorial/build
cmake ..
make
gazebo --verbose ../velodyne.world
  • 新建终端,进入文件所在目录并运行vel命令,确保设置数值,该数值被解释为目标速度值。
cd ~/gazebo_plugin_tutorial/build/
./vel 2

总结

  • 内容分析:本篇博客主要介绍了Gazebo中系统插件的使用和配置方法,并且重点从头到尾分析研究了velodyne传感器插件的配置、设计、测试流程,最后针对于插件调整设计了两种API,完成了编程后的便捷使用,与上节博客一起完成了对于Gazebo仿真插件的使用教程介绍。

在这里插入图片描述

  • 注意:本文参考了Gazebo官方网站以及古月居中的Gazebo有关教程、知乎Bruce的教程等,主要目的是方便自行查询知识,巩固学习经验,无任何商业用途。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

【Gazebo入门教程】第六讲 控制器插件的编写与配置(下) 的相关文章

  • Linux虚拟机在线扩容lvm类型root分区

    目录 Linux虚拟机在线扩容lvm类型root分区写在前面正文写在后面 Linux虚拟机在线扩容lvm类型root分区 写在前面 这是我在CSDN上的第一篇文章 作为一个半江湖的IT人 xff0c 这些年来也在CSDN受益很多 今天是20
  • 无vCenter创建vSAN集群

    无vCenter创建vSAN集群 最近仍有朋友在问题 xff0c vCenter如果 挂了 xff0c vSAN还能正常运行吗 xff1f 这个小文通过手动创建vSAN集群的方式来解答下这个问题吧 xff08 生产环境慎用 xff01 xf
  • 记一次mdadm软raid1升级容量

    MDRaid 2块4TB做了软RAID1 xff0c 需要升级成2块8TB盘 查看磁盘信息 xff0c SerialNumber等会儿会用到 xff0c 防止换错盘 span class token function sudo span h
  • [简洁版]youtube-dl下载命令

    简介 YouTube dl是python上的pip模块 xff08 开源 xff09 xff0c 可以用来下载YouTube Bilibili等多个平台的视频 音频文件 xff0c 可谓是居家旅行必备小工具 本文主要介绍一些常用的youtu
  • [简版]VMware强大的管理工具-PowerCLI

    一 PowerCLI介绍 什么是 PowerCLI PowerCLI 是一个命令行工具 xff0c 可以用于自动化vSphere管理 xff0c 包括网络 存储 虚拟机以及其他很多功能 PowerCLI包含超过700个命令 要安装Power
  • [简版]使用PowerCLI自定义vSphere ISO安装镜像

    一 什么情况下要自定义ISO镜像 一般来说 xff0c 对于DELL Lenovo HPE这类主流的服务器厂商 xff0c VMware官方vSphere ISO镜像或者官网的第三方客制镜像 xff08 由服务器厂商提供的封装镜像 xff0
  • [简版] 关于vSphere漏洞-OpenSLP

    一 前言 近期vSphere OpenSLP漏洞在野利用的新闻频频被爆出来 xff0c 大伙儿非常关注 由于vSphere虚拟化客户之广泛 xff0c 很多朋友都表达了自己的焦虑 xff0c 同时也会担心自己管理的vSphere虚拟化平台是
  • [简版] Linux搭建SAMBA文件共享服务

    SMB服务搭建 更多参数含义参考链接 常用配置 安装samba span class token comment Ubuntu span span class token function sudo span span class toke
  • STM32 HAL库详解

    STM32 HAL库整体总结 STM32 之二 HAL库详解 及 手动移植 本篇博客是对以上参考资源的一个二次总结与整理 1 HAL库文件结构 对于开发人员而言 xff0c 首先要清楚 HAL 库的文件结构 根据文件类型可认为以下两大类 x
  • STM32 HAL库学习(四):DMA之串口空闲中断

    STM32CubeMX 配置实现参考这里 1 串口空闲中断 1 1 UART DMA方式接收数据 STM32串口使用DMA方式接收数据可以减小CPU的开销 对于接收定长数据 xff0c 可以将DMA接收缓冲区的长度设定为待接收数据的长度 x
  • Android Studio 启动模拟器出现“Timed out after 300seconds waiting for emulator to come online“解决方案

    Android Studio 启动模拟器出现 34 Timed out after 300seconds waiting for emulator to come online 34 解决方案 参考文章 xff1a xff08 1 xff0
  • 结构体中的位定义

    1 结构体位定义 在工作中 xff0c 经常遇到按位 xff08 bit xff09 定义结构体 的情况 由于一个字节有8个位 xff0c 这时 xff0c 程序员往往对bit的位置产生困惑 现在给出2个例子 xff0c 来说明位的定义次序
  • 蓝牙基础(三):蓝牙协议栈总体认知

    蓝牙基础 xff08 三 xff09 xff1a 蓝牙协议栈总体认知 0 前言 初入门经典蓝牙学习 xff0c 网上资料参差不齐 xff0c 本博客旨在整理自己的一些总结内容 xff0c 建立整体功能认识 xff0c 以便后续深入学习 1
  • FreeRTOS学习(四)任务调度与切换

    文章目录 1 任务调度2 任务切换2 1 SVC 和 PendSV2 2 上下文2 3 切换场景2 4 PendSV Handler 3 总结 1 任务调度 在建立完任务后紧接着调用任务调度函数 xff0c 便会使系统运行起来 span c
  • FreeRTOS学习(五)队列与信号量

    文章目录 1 队列1 1 队列特性1 2 队列创建1 2 1 接口函数1 2 2 内存占用1 2 3 创建过程分析 1 3 入队与出队1 3 1 队列项入队1 3 1 队列项出队 2 信号量2 1 二值信号量2 2 计数型信号量2 3 互斥
  • FreeRTOS学习(六)时间管理

    文章目录 1 延时函数1 1 vTaskDelay 1 2 vTaskDelayUntil 1 3 系统时钟节拍 2 软件定时器2 1 定时器概述2 2 定时器 API 3 总结 1 延时函数 当任务需要调用延时函数延时时 xff0c 任务
  • C语言 sscanf库函数

    目录 1 函数描述2 函数应用2 1 基础应用2 2 高级应用 1 函数描述 xff08 1 xff09 函数功能 xff1a 通常被用来解析并转换字符串 xff0c 从str指定的字符串读取数据 xff0c 并根据参数format字符串来
  • C语言 文件读写

    目录 1 文件打开与关闭1 1 打开文件 fopen 1 2 关闭文件 fclose 2 读取文件2 1 fgetc 2 2 fgets 2 3 fscanf 3 写入文件3 1 fputc 3 2 fputs 3 3 fprintf 1
  • C语言 条件编译

    目录 1 if elif else endif 2 ifdef else endif 3 ifndef else endif 4 三者区别 根据不同情况编译不同代码 产生不同目标文件的机制 xff0c 称为条件编译 条件编译是预处理程序的功
  • yolo 学习系列(三):训练参数与网络参数

    yolo 学习系列 xff08 三 xff09 xff1a 训练参数与网络参数 手把手教你做目标检测 xff08 YOLO SSD xff09 视频链接 1 训练参数 博主在使用 yolov2 tiny voc 训练 人 这一类目标物体时

随机推荐