ROS学习笔记09、ROS进阶(Action通信、动态参数、pluginlib、nodelet)

前言

马上开学,目前学校很多实验室都是人工智能这块,大部分都是和机器人相关,然后软件这块就是和cv、ros相关,就打算开始学习一下。

本章节是虚拟机安装Ubuntu18.04以及安装ROS的环境。

学习教程:【Autolabor初级教程】ROS机器人入门,博客中一些知识点是来源于赵老师的笔记在线笔记,本博客主要是做归纳总结,如有侵权请联系删除。

视频中的案例都基本敲了遍,这里给出我自己的源代码文件:

链接:https://pan.baidu.com/s/13CAzXk0vAWuBsc4oABC-_g 
提取码:0hws 

所有博客文件目录索引:博客目录索引(持续更新)

一、Action通信

1.1、认识Action通信

介绍

技术产生缘由:若是某一个请求需要处理的时间过程,也就是说响应因为处理时间长导致未能快速响应返回,就可能出现程序"假死"的现象。

  • 比较合理的效果:导航过程中,可以连续反馈当前机器人状态信息,当导航终止时,再返回最终的执行结果。

Action通信特点:在一个请求响应过程中间添加连续反馈及响应。

原理

action结构:

image-20220919210738590

action通信接口图解:

image-20220919210803265

  • goal:目标任务;
  • cacel:取消任务;
  • status:服务端状态;
  • result:最终执行结果(只会发布一次);
  • feedback:连续反馈(可以发布多次)

1.2、实践

新建工程包:

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_action的包
catkin_create_pkg --rosdistro melodic demo_action roscpp rospy std_msgs actionlib actionlib_msgs

自定义Action文件

image-20220918191644374

AddInts.action

#目标值goal
int32 num
---
#最终结果result
int32 result
---
#连续反馈Feedback
float64 progress_bar

接着配置CMakeLists.txt:

find_package(catkin REQUIRED COMPONENTS
  actionlib
  actionlib_msgs
  roscpp
  rospy
  std_msgs
)

add_action_files(
  FILES
  AddInts.action
)

generate_messages(
  DEPENDENCIES
  std_msgs
  actionlib_msgs
)

catkin_package(
#  INCLUDE_DIRS include
#  LIBRARIES demo_action
   CATKIN_DEPENDS actionlib actionlib_msgs roscpp rospy std_msgs
#  DEPENDS system_lib
)

此时我们来编译整个工程文件,目的就是来得到一些消息的结构体文件以及C++、Python的调用文件。

消息结构体文件:包含之前Action文件的一些数据信息,拆分编译得到了多个结构体供之后进行调用,在share目录

image-20220918191909497

C++调用文件:在devel/include目录

image-20220918192133295

Python调用文件:在devel/lib目录

image-20220918192234062

action通信实现(C++)

服务端

image-20220918201520662

demo01_action_server.cpp

#include "ros/ros.h"
#include "actionlib/server/simple_action_server.h"
#include "demo_action/AddIntsAction.h"

//封装
typedef actionlib::SimpleActionServer<demo_action::AddIntsAction> Server;

void cb(const demo_action::AddIntsGoalConstPtr &goal, Server* server) {
    //获取到目标值
    int num = goal->num;
    ROS_INFO("目标值:%d", num);
    //累加来进行连续反馈
    int result = 0;
    demo_action::AddIntsFeedback feedback;//用于连续反馈的消息对象
    ros::Rate rate(10);
    for (int i = 0; i <= num; i++) {
        result += i;
        //进度数据
        feedback.progress_bar = i / (double)num;
        //组织连续数据发布
        server->publishFeedback(feedback);
        rate.sleep();
    }
    //最终处理结果后发送最终结果集
    demo_action::AddIntsResult r;
    r.result = result;
    server->setSucceeded(r);
    ROS_INFO("最终结果为:%d", r.result);
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    ROS_INFO("action服务端启动...");
    //1、初始化ros节点
    ros::init(argc, argv, "AddInts_server");
    //2、初始化NodeHandle
    ros::NodeHandle nh;
    //3、创建action服务对象
    /* SimpleActionServer(ros::NodeHandle n, 
                    std::string name, 
                    boost::function<void (const demo01_action::AddIntsGoalConstPtr &)> execute_callback, 
                    bool auto_start)  
        参数1:nodehandle
        参数2:action服务名称
        参数3:回调函数
        参数4:是否自动启动
    */
    // actionlib::SimpleActionServer<demo_action::AddIntsAction> server
    Server server(nh, "addInts", boost::bind(&cb, _1, &server), false);
    server.start();
    //4、处理请求,进行反馈与响应
    ros::spin();

    return 0;
}

运行节点:

# 启动roscore核心
roscore

source ./devel/setup.bash

# 运行server服务节点
rosrun demo_action demo01_action_server

image-20220918201555538

# 查看话题列表
rostopic list

image-20220918195019841

测试

客户端测试

接着我们可以尝试去使用命令行像/addInts/goal使用指定的结构体来发布消息:

source ./devel/setup.bash

# rostopic pub 主题发布   /addInts/goal表示主题目标  demo_action/AddIntsActionGoal消息结构体
# 最后就消息结构
rostopic pub /addInts/goal demo_action/AddIntsActionGoal "header:
  seq: 0
  stamp:
    secs: 0
    nsecs: 0
  frame_id: ''
goal_id:
  stamp:
    secs: 0
    nsecs: 0
  id: ''
goal:
  num: 100" 

此时在服务端就可以收到消息:

image-20220918201018365

获取反馈数据响应:也就是一个消息在完成前可以拿到的数据反馈

source ./devel/setup.bash

# 接收连续反馈数据
rostopic echo /addInts/feedback

image-20220918201259221

  • 其中progress_bar就是不断连续反馈得到的结果值。

客户端

image-20220918202921373

demo01_action_client.cpp

#include "ros/ros.h"
#include "actionlib/client/simple_action_client.h"
#include "demo_action/AddIntsAction.h"

//封装
typedef actionlib::SimpleActionClient<demo_action::AddIntsAction> Client;

//最终处理结果函数
//参数:结果状态对象  结果集
void done_cb (const actionlib::SimpleClientGoalState &state, const demo_action::AddIntsResultConstPtr &result) {
    if (state.state_ == state.SUCCEEDED) {
        ROS_INFO("最终结果为:%d", result->result);
    }else {
        ROS_INFO("任务失败!");
    }
}

//服务激活
void active_cb() {
    ROS_INFO("服务已被激活!");
}

//处理连续反馈
void feedback_cb (const demo_action::AddIntsFeedbackConstPtr &feedback) {
    ROS_INFO("当前进度:%.2f", feedback->progress_bar);
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "AddInts_client");
    ros::NodeHandle nh;
    //创建action客户端对象
    // SimpleActionClient(ros::NodeHandle & n, const std::string & name, bool spin_thread = true)
    Client client(nh, "addInts", true);
    //等待服务启动
    client.waitForServer();
    //发送目标,处理反馈以及最终结果
    /*  
        void sendGoal(const demo01_action::AddIntsGoal &goal, 
            boost::function<void (const actionlib::SimpleClientGoalState &state, const demo01_action::AddIntsResultConstPtr &result)> done_cb, 
            boost::function<void ()> active_cb, 
            boost::function<void (const demo01_action::AddIntsFeedbackConstPtr &feedback)> feedback_cb)
    */
   demo_action::AddIntsGoal goal;
   goal.num = 10;
   //向服务发送数据
   client.sendGoal(goal, &done_cb, &active_cb, &feedback_cb);
   //回调函数处理
   ros::spin();

    return 0;
}

修改CMakeLists.txt配置:

add_executable(demo01_action_client src/demo01_action_client.cpp)

add_dependencies(demo01_action_client ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo01_action_client
  ${catkin_LIBRARIES}
)

编译项目,接着先启动服务节点,在运行client节点:

# 启动roscore核心
roscore

source ./devel/setup.bash

# 运行server服务节点
rosrun demo_action demo01_action_server

image-20220918203240856

# 启动client节点来进行发送数据
rosrun demo_action demo01_action_client

image-20220918202908902

image-20220918203334017

二、动态参数

2.1、认识动态参数

此前出现描述:再此之前我们学习的param server,每次去get得到相应的参数值之后,该参数值就已经直接保存到了内存,若是我们此时去修改param server的参数值,那么此时原本程序之前获取到的值并没有能够进行刷新,除非重启才有效果!

实际效果:实时动态更新参数。

应用场景:全局地图中就使用到了动态参数,可使用rqt来进行动态修改。

原理分析:动态配置参数,之所以能够实现即时更新,因为被设计成 CS 架构,客户端修改参数就是向服务器发送请求,服务器接收到请求之后,读取修改后的是参数。

2.2、实际应用演示效果

案例效果演示:全局地图动态参数配置修改。

借助于之前的案例,我们来直接启动仿真环境:

# 启动gazebo(第7章节部分的,复用之前的)
roslaunch 07urdf_gazebo demo03_evn.launch

# 深度图像转激光数据
roslaunch 08nav_demo nav07_depthimage_to_laserscan.launch

# 运行路径规划测试launch(地图加载、amcl、movebase、rviz)
roslaunch 08nav_demo nav05_path_test.launch

打开rqt:

rqt

image-20220919211413916

选择其中``plugins->configuration->Dynamic Reconfigure`,接着我们来修改inflation_radius的值即可,只要修改好配置之后,可以看到rviz中的地图效果也发生了改变!

image-20220919211611959

2.2、实践动态参数

动态参数客户端

客户端实现流程:

  • 新建并编辑 .cfg 文件;
  • 编辑CMakeLists.txt;
  • 编译。

新建功能包:

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_dynamicparameter的包
catkin_create_pkg --rosdistro melodic demo_dynamicparameter roscpp rospy std_msgs dynamic_reconfigure

步骤一:新建cfg文件夹与创建cfg配置文件

image-20220918204320520

dr.cfg:本质就是python代码,主要用于生成对应的消息结构体

#! /usr/bin/env python
# coding=utf-8

# 1.导包
from dynamic_reconfigure.parameter_generator_catkin import *
PACKAGE = "demo_dynamicparameter"
# 2.创建生成器
gen = ParameterGenerator()

# 3.向生成器添加若干参数
#add(name, paramtype, level, description, default=None, min=None, max=None, edit_method="")
gen.add("int_param" ,int_t ,0 ,"整型参数" ,50 ,0 ,100)
gen.add("double_param",double_t,0,"浮点参数",1.57,0,3.14)
gen.add("string_param",str_t,0,"字符串参数","hello world ")
gen.add("bool_param",bool_t,0,"bool参数",True)
many_enum = gen.enum([gen.const("small",int_t,0,"a small size"),
                gen.const("mediun",int_t,1,"a medium size"),
                gen.const("big",int_t,2,"a big size")
                ],"a car size set")
gen.add("list_param",int_t,0,"列表参数",0,0,2, edit_method=many_enum)

# 4.生成中间文件并退出
# generate(pkgname, nodename, name)
# 分别是包名、节点名以及cfg名称
exit(gen.generate(PACKAGE,"dr_client","dr"))

步骤二:修改CMakeLists.txt文件

generate_dynamic_reconfigure_options(
  cfg/dr.cfg
)

接着我们编译整个项目,此时就会根据我们的cfg文件来进行生成相应的动态参数客户端。

编译得到的文件内容如下:

c++的头文件

image-20220918204615202

python的配置文件

image-20220918204653984


动态参数服务端(C++)

服务端实现流程:

  • 新建并编辑 c++ 文件;
  • 编辑CMakeLists.txt;
  • 编译并执行。

image-20220918210944913

demo_dr_server.cpp·:

#include "ros/ros.h"
#include "dynamic_reconfigure/server.h"
#include "demo_dynamicparameter/drConfig.h"

//回调函数
void cb (demo_dynamicparameter::drConfig& config, uint32_t level) {
    ROS_INFO("动态参数解析数据:%d,%.2f,%d,%s,%d",
        config.int_param,
        config.double_param,
        config.bool_param,
        config.string_param.c_str(),
        config.list_param
    );
}

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    ros::init(argc, argv, "dr");
    //1、创建服务器对象(demo_dynamicparameter::drConfig)
    dynamic_reconfigure::Server<demo_dynamicparameter::drConfig> server;
    //2、创建回调函数(使用回调函数,打印修改后的参数)
    dynamic_reconfigure::Server<demo_dynamicparameter::drConfig>::CallbackType cbType;
    cbType = boost::bind(&cb, _1, _2);
    //3、服务器对象采用回调函数
    server.setCallback(cbType);
    //4、回旋回调函数
    ros::spin();
    return 0;
}

修改CMakeLists.txt配置文件:

add_executable(demo_dr_server src/demo_dr_server.cpp)

add_dependencies(demo_dr_server ${${PROJECT_NAME}_EXPORTED_TARGETS} ${catkin_EXPORTED_TARGETS})

target_link_libraries(demo_dr_server
  ${catkin_LIBRARIES}
)

接着编译整个项目,运行结点:

# 启动roscore核心
roscore

source ./devel/setup.bash

# 运行server服务节点
rosrun demo_dynamicparameter demo_dr_server

我们可以来进行在线测试,来对一些参数来进行修改,看我们的服务端是否能够进行响应:

# 快捷打开 或者rqt来指定打开
rosrun rqt_gui rqt_gui -s rqt_reconfigure

image-20220918211435347

修改某个参数,此时就会在客户端有响应:

image-20220918211452275

三、pluginlib

3.1、认识pluginlib

介绍pluginlib是一个c++库, 用来从一个ROS功能包中加载和卸载插件(plugin)。插件是指从运行时库中动态加载的类。通过使用Pluginlib,不必将某个应用程序显式地链接到包含某个类的库,Pluginlib可以随时打开包含类的库,而不需要应用程序事先知道包含类定义的库或者头文件。

  • pluginlib直译是插件库,所谓插件字面意思就是可插拔的组件。

基本原理:就是通过规范化的USB接口协议实现计算机与USB设备的自由组合。同理,在软件编程中,插件是一种遵循一定规范的应用程序接口编写出来的程序,插件程序依赖于某个应用程序,且应用程序可以与不同的插件程序自由组合。

实际场景

1.导航插件:在导航中,涉及到路径规划模块,路径规划算法有多种,也可以自实现,导航应用时,可能需要测试不同算法的优劣以选择更合适的实现,这种场景下,ROS中就是通过插件的方式来实现不同算法的灵活切换的。

2.rviz插件:在rviz中已经提供了丰富的功能实现,但是即便如此,特定场景下,开发者可能需要实现某些定制化功能并集成到rviz中,这一集成过程也是基于插件的。

3.2、实操

实现流程:

  1. 准备;
  2. 创建基类;
  3. 创建插件类;
  4. 注册插件;
  5. 构建插件库;
  6. 使插件可用于ROS工具链;
    • 配置xml
    • 导出插件
  7. 使用插件;
  8. 执行。

3.2.1、注册插件

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_pluginlib的包
catkin_create_pkg --rosdistro melodic demo_pluginlib roscpp pluginlib

202209182139621

dbx_base.h:基类编写,定义两个方法,之后的实现类去实现相应的方法

#ifndef DBX_BASE_H
#define DBX_BASE_H

namespace dbx_base_ns {
    class Dbx_Base {
        protected: 
            Dbx_Base() {}
        
        public:
            //计算周长的函数
            virtual double getLength() = 0;
            //初始化边长的函数
            virtual void init(double side_length) = 0;
    };
}

#endif

dbx_plugins.h:两个实现类

#ifndef DBX_PLUGINS_H
#define DBX_PLUGIN_H
#include "demo_pluginlib/dbx_base.h"

namespace dbx_plugins_ns {
    //三边
    class SanBian: public dbx_base_ns::Dbx_Base {
        private:
            double side_length;
        public:
            SanBian(){
                side_length = 0.0;
            }
            void init(double size_length) {
                this->side_length = side_length;
            }
            double getLength() {
                return side_length * 3;
            }
    };
    //四边
    class SiBian: public dbx_base_ns::Dbx_Base {
        private:
            double side_length;
        public:
            SiBian(){
                side_length = 0.0;
            }
            void init(double size_length) {
                this->side_length = side_length;
            }
            double getLength() {
                return side_length * 4;
            }
    };
}

#endif

plus.cpp:注册插件

#include "pluginlib/class_list_macros.h"
#include "demo_pluginlib/dbx_base.h"
#include "demo_pluginlib/dbx_plugins.h"

//参数1:子类  参数2:父类
PLUGINLIB_EXPORT_CLASS(dbx_plugins_ns::SanBian, dbx_base_ns::Dbx_Base)
PLUGINLIB_EXPORT_CLASS(dbx_plugins_ns::SiBian, dbx_base_ns::Dbx_Base)

接着我们来修改CMakeLists.txt:

include_directories(
  include
  ${catkin_INCLUDE_DIRS}
)

# 指定我们的plus.cpp文件
add_library(plus
  src/plus.cpp
)
  • 上面放开的include就是我们之前在include目录下添加的文件内容。

接着我们来编译整个项目,该文件会将两个衍生类注册为插件,即生成相关的.so文件。

image-20220918212848307

image-20220918213129671

3.2.2、将构建的插件用于ROS工具链

接下来工作:将插件用于ROS工具链。

image-20220918215400313

plus.xml:编写我们对应的libplus以及相应的实现类、基类

<!-- 插件库的相对路径
    path:指的是devel/lib/libplus.so
 -->
<library path="lib/libplus">
  <!-- type="插件类" base_class_type="基类"
      命名空间:相应类名
   -->
  <class type="dbx_plugins_ns::SanBian" base_class_type="dbx_base_ns::Dbx_Base">
    <!-- 描述信息 -->
    <description>这是一个三边类</description>
  </class>
  <class type="dbx_plugins_ns::SiBian" base_class_type="dbx_base_ns::Dbx_Base">
    <description>这是一个四边类</description>
  </class>
</library>

在package.xml中进行导出插件配置:

<export>
    <!-- Other tools can request additional information be placed here -->
    <!-- 标签名指的是包名,plugin就是对应的插件目录 -->
    <demo_pluginlib plugin="${prefix}/plus.xml" />
</export>

接下来我们就可以使用catkin_make来进行编译整个工程,编译完成之后我们可以来使用rospack工具来检查下是否已经将插件集成到了ROS工具链:

rospack help

# demo_pluginlib就是当前的包名
rospack plugins --attrib=plugin demo_pluginlib

image-20220918215734991

若是返回 .xml 文件的完整路径,这意味着插件已经正确的集成到了ROS工具链。


3.3.3、实际使用插件

image-20220918221027686

use_plugin.cpp:使用类加载来进行加载插件

#include "ros/ros.h"
#include "pluginlib/class_loader.h"
#include "demo_pluginlib/dbx_base.h"

int main(int argc, char *argv[])
{
    setlocale(LC_ALL, "");
    //1、创建类加载器
    pluginlib::ClassLoader<dbx_base_ns::Dbx_Base> loader("demo_pluginlib", "dbx_base_ns::Dbx_Base");

    //2、使用类加载实例化某个插件对象
    //注册插件:san
    boost::shared_ptr<dbx_base_ns::Dbx_Base> san = loader.createInstance("dbx_plugins_ns::SanBian");

    //3、使用插件
    san->init(10);
    double length = san->getLength();
    ROS_INFO("三角形周长:%.2f", length);

    //测试:注册插件 sibian
    boost::shared_ptr<dbx_base_ns::Dbx_Base> si = loader.createInstance("dbx_plugins_ns::SiBian");
    si->init(10);
    length = si->getLength();
    ROS_INFO("四边形周长:%.2f", length);

    return 0;
}

编译整个工程,然后去运行节点:

source ./devel/setup.bash

rosrun demo_pluginlib use_plugin

image-20220918221123967

四、nodelet

4.1、认识nodelet

解决问题:实际问题就是在不同进程之间数据交互产生的延时与阻塞问题。

  • 不同Node之间数据交互其实是不同进程之间的数据交互,当传输类似于图片、点云的大容量数据时,会出现延时与阻塞的情况,

实际场景:现在需要编写一个相机驱动,在该驱动中有两个节点实现:其中节点A负责发布原始图像数据,节点B订阅原始图像数据并在图像上标注人脸。如果节点A与节点B仍按照之前实现,两个节点分别对应不同的进程,在两个进程之间传递容量可观图像数据,可能就会出现延时的情况,那么该如何优化呢?

Nodelet介绍

  1. nodelet软件包旨在提供在同一进程中运行多个算法(节点)的方式,不同算法之间通过传递指向数据的指针来代替了数据本身的传输(类似于编程传值与传址的区别),从而实现零成本的数据拷贝。
  2. 通过Nodelet可以将多个节点集成进一个进程。

核心实现:同样也是插件,是对插件的进一步封装。

  • 不同算法被封装进插件类,可以像单独的节点一样运行;
  • 在该功能包中提供插件类实现的基类:Nodelet;
  • 并且提供了加载插件类的类加载器:NodeletLoader。

效果:应用于大容量数据传输的场景,提高节点间的数据交互效率,避免延时与阻塞。

4.2、命令行演示—基于nodelet_tutorial_math/Plus

效果:通过两个节点来实现数据相加并进行通信。

完整命令行展示

roscore

# 启动节点服务,命名为mymanager
rosrun nodelet nodelet manager __name:=mymanager

# 测试节点1
# nodelet_tutorial_math/Plus:指的是运行的指定节点插件
# __name:=n1:表示设置别名n1,对应发布的主题为/n1/in、/n1/out
# _value:=100:向param服务器中存储key为value,value值为100
rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n1 _value:=100

rostopic list

# 向节点1按照一定的频率(n1/in,相当于入口)来发布消息
rostopic pub -r 10 /n1/in std_msgs/Float64 "data: 10.0"

# 看对应n1的出口是否有响应值
rostopic echo /n1/out


# 测试节点2
# __name:=n2:表示设置别名n2,对应发布的主题为/n2/in、/n2/out
# _value:=-50:向param服务器中存储key为value,value值为-50
rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n2 _value:=-50 /n2/in:=/n1/out

rostopic list

# 来看n2的出口的结果值是否正确
rostopic echo /n2/out

实操命令+效果截图演示

打开多个窗口,其中没有划横线的就是测试窗口:

image-20220919204956583

首先启动``roscore`。

接着来启动管理器:

rosrun nodelet nodelet manager __name:=mymanager

image-20220919205310528

此时我们来启动节点1:

rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n1 _value:=100

image-20220919205327940

此时我们来看一看主题节点的情况:

rostopic list

image-20220919205510875

接着我们来测试节点1,尝试向节点1的主题/n1/in来发送数据,接着去打印/n1/out的输出信息:

rostopic pub -r 10 /n1/in std_msgs/Float64 "data: 10.0"

rostopic echo /n1/out

image-20220919205539566

image-20220919205557420

节点1的输入与输出都正常,来启动节点2:

rosrun nodelet nodelet load nodelet_tutorial_math/Plus mymanager __name:=n2 _value:=-50 /n2/in:=/n1/out

image-20220919205711825

接着我们来看节点1的out是否转发到了结点2的in:

rostopic echo /n2/out

image-20220919205731428

ok,最终我们演示结束!


4.3、自定义代码实操(实现4.2的节点通信效果)

实现目的:与4.2效果一致,能够实现中间数相加进行数据传递。

4.3.1、创建工程包

# 进入到工程下的src目录
cd /home/workspace/roslearn/src

# 创建名为demo_nodelet的包
catkin_create_pkg --rosdistro melodic demo_nodelet roscpp nodelet

4.3.2、自定义构建插件库(基于nodelet)

image-20220919180048159

myplus.cpp:编写代码

#include "ros/ros.h"
#include "nodelet/nodelet.h"
#include "pluginlib/class_list_macros.h"
#include "std_msgs/Float64.h"

namespace my_nodelet {
    class MyPlus: public nodelet::Nodelet {
        private:
            ros::Publisher pub;
            ros::Subscriber sub;
            double value;

        public:
            MyPlus(){
                value = 0.0;
            }
            void onInit() {
                ROS_INFO("MyPlus init ...");
                //2、获取NodeHandle
                ros::NodeHandle nh = getPrivateNodeHandle();
                //3、通过NodeHandle创建订阅对象和发布对象,解析参数
                nh.getParam("value", value);
                //发布
                pub = nh.advertise<std_msgs::Float64>("out", 100);//话题名称:/节点名/out
                //订阅(in就是传入的数据,value就是本身发布结点时存储参数服务器的数据)
                sub = nh.subscribe<std_msgs::Float64>("in", 100, &MyPlus::cb, this);
                //4、订阅对象的回调函数需要处理数据,并通过发布对象发布
            }
            //处理订阅的回调函数
            void cb(const std_msgs::Float64::ConstPtr& p) {
                //1、解析订阅的数据
                double in = p->data;
                //2、和参数相加
                double out = in + value;
                //3、发布
                std_msgs::Float64 out_msg;
                out_msg.data = out;
                pub.publish(out_msg);
            }
    };
}

PLUGINLIB_EXPORT_CLASS(my_nodelet::MyPlus, nodelet::Nodelet)

构建插件库,修改CMakeLists.txt:

add_library(myplus
  src/myplus.cpp
)

target_link_libraries(myplus
  ${catkin_LIBRARIES}
)

此时进行编译,就会在lib目录下得到一个so文件:

image-20220919180448061

4.3.3、插件作用ROS工具链

将插件应用于Ros工具链:

在包目录下创建配置文件:myplus.xml

<library path="lib/libmyplus" >
    <!-- 
        class.type表示的是:命名空间::类。myplus.cpp中最后一行PLUGINLIB_EXPORT_CLASS(my_nodelet::MyPlus, nodelet::Nodelet),中的【my_nodelet::MyPlus】
        base_class_type:就是固定的:nodelet::Nodelet
        name:一般是包名/类名
    -->
    <class type="my_nodelet::MyPlus" base_class_type="nodelet::Nodelet" name="demo_nodelet/Myplus">
            <description>我的nodelet测试节点</description>
    </class>
</library>

修改package.xml:

<export>
    <!-- 标签名为基类,plugin表示为路径 -->
    <nodelet plugin="${prefix}/myplus.xml" />
</export>

此时来去进行编译,即可应用于工具类。


4.3.4、最终测试

完整测试代码:

# 测试工具链(需要去查基类的工具包),若是返回相应的xml就没有问题
rospack plugins --attrib=plugin nodelet

rosrun nodelet nodelet manager __name:=mymanager

# 测试节点1
# load后指定的在myplus的配置文件中来进行引入(demo_nodelet/Myplus)
rosrun nodelet nodelet load demo_nodelet/Myplus mymanager __name:=plus _value:=-50

rostopic list

rostopic pub -r 10 /plus/in std_msgs/Float64 "data: 10.0"

# 测试节点2
# 预计:-40
rostopic echo /plus/out  

rosrun nodelet nodelet load demo_nodelet/Myplus mymanager __name:=plus2 _value:=100 /plus2/in:=/plus/out

rostopic list

# 预计:60
rostopic echo /plus2/out

上述的完整测试代码实际与4.2中调用工具包是效果一致的,这里不再重复演示效果!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

长路 ㅤ   

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值